--- bochs/iodev/serial.cc	2022-05-06 07:31:17.780238890 +0700
+++ bochs/iodev/serial.cc	2022-09-08 07:37:53.459246721 +0700
@@ -74,6 +74,8 @@
   "socket-server",
   "pipe-client",
   "pipe-server",
+  "null-modem",
+  "virtual-modem",
   NULL
 };
 
@@ -222,7 +224,17 @@
           break;
         case BX_SER_MODE_SOCKET_CLIENT:
         case BX_SER_MODE_SOCKET_SERVER:
-          if (BX_SER_THIS s[i].socket_id >= 0) closesocket(BX_SER_THIS s[i].socket_id);
+        case BX_SER_MODE_NULL_MODEM:
+        case BX_SER_MODE_VIRTUAL_MODEM:
+          if (BX_SER_THIS s[i].socket_id >= 0) {
+            BX_INFO(("com%d: closing connection", i+1));
+            closesocket(BX_SER_THIS s[i].socket_id);
+            BX_SER_THIS s[i].modem_status.dcd = 0;
+            BX_SER_THIS s[i].modem_status.delta_dcd = 1;
+            BX_SER_THIS s[i].socket_id = BX_SER_THIS s[i].server_socket_id;
+            BX_SER_THIS s[i].ms_ipending = 1;
+            raise_interrupt(i, BX_SER_INT_MODSTAT);
+          }
 #ifdef BX_SER_WIN32
           if (winsock_init) {
             WSACleanup();
@@ -254,6 +266,8 @@
   char name[16], pname[20];
   bx_list_c *base, *misc_rt = NULL, *menu = NULL;
   unsigned i, count = 0;
+  int res = 0;
+  int nonb = 0;
 
   BX_SER_THIS detect_mouse = 0;
   BX_SER_THIS mouse_port = -1;
@@ -370,6 +384,40 @@
       BX_SER_THIS s[i].baudrate = 115200;
       BX_SER_THIS s[i].databyte_usec = 87;
 
+      /* Initialize the virtual modem */
+      BX_SER_THIS s[i].virtual_modem.at_cmd_buf_idx = 0;
+      BX_SER_THIS s[i].virtual_modem.at_cmd_buf[0] = 0;
+      BX_SER_THIS s[i].virtual_modem.last_ring_time = 0;
+      BX_SER_THIS s[i].virtual_modem.tx_buffer_idx = 0;
+      BX_SER_THIS s[i].virtual_modem.tx_buffer_length = 0;
+      BX_SER_THIS s[i].virtual_modem.tx_buffer_send_time = 0;
+
+      /* Virtual modem AT toggles */
+      BX_SER_THIS s[i].virtual_modem.echo = false;
+      /* We keep the modem off hook by default.
+         This will deny connections until the software is ready. */
+      BX_SER_THIS s[i].virtual_modem.off_hook = true;
+      BX_SER_THIS s[i].virtual_modem.quiet = false;
+      BX_SER_THIS s[i].virtual_modem.verbose = true;
+
+      /* Virtual modem "S" registers */
+      BX_SER_THIS s[i].virtual_modem.auto_answer = 0;
+      BX_SER_THIS s[i].virtual_modem.rings_sent = 0;
+      BX_SER_THIS s[i].virtual_modem.esc_char = '+';
+      BX_SER_THIS s[i].virtual_modem.cr_char = '\r';
+      BX_SER_THIS s[i].virtual_modem.lf_char = '\n';
+      BX_SER_THIS s[i].virtual_modem.bs_char = 8;
+      BX_SER_THIS s[i].virtual_modem.blind_dial_wait = 0;
+      BX_SER_THIS s[i].virtual_modem.dial_carrier_wait = 0;
+      BX_SER_THIS s[i].virtual_modem.comma_pause_time = 2;
+      BX_SER_THIS s[i].virtual_modem.carrier_detect_time = 0;
+      BX_SER_THIS s[i].virtual_modem.no_carrier_hangup_time = 14;
+      BX_SER_THIS s[i].virtual_modem.escape_guard_time = 50;
+      BX_SER_THIS s[i].virtual_modem.at_delay = 0;
+      BX_SER_THIS s[i].virtual_modem.delay_to_dtr = 5;
+      BX_SER_THIS s[i].virtual_modem.rts_to_cts_delay = 1;
+      BX_SER_THIS s[i].virtual_modem.inactivity_timer = 0;
+
       for (unsigned addr = ports[i]; addr < (unsigned)(ports[i] + 8); addr++) {
         BX_DEBUG(("com%d initialize register for read/write: 0x%04x", i + 1, addr));
         if (addr < (unsigned)(ports[i] + 7)) {
@@ -448,14 +496,21 @@
         BX_SER_THIS s[i].io_mode = BX_SER_MODE_MOUSE;
         BX_SER_THIS mouse_port = i;
         BX_SER_THIS mouse_type = SIM->get_param_enum(BXPN_MOUSE_TYPE)->get();
-      } else if ((mode == BX_SER_MODE_SOCKET_CLIENT) ||
-                 (mode == BX_SER_MODE_SOCKET_SERVER)) {
+      } else if (   (mode == BX_SER_MODE_SOCKET_CLIENT)
+                 || (mode == BX_SER_MODE_SOCKET_SERVER)
+                 || (mode == BX_SER_MODE_NULL_MODEM)
+                 || (mode == BX_SER_MODE_VIRTUAL_MODEM)
+      ) {
         struct sockaddr_in  sin;
         struct hostent      *hp;
         char                host[BX_PATHNAME_LEN];
         int                 port;
         SOCKET              socket;
-        bool                server = (mode == BX_SER_MODE_SOCKET_SERVER);
+        bool                server = (
+               (mode == BX_SER_MODE_SOCKET_SERVER)
+            || (mode == BX_SER_MODE_NULL_MODEM)
+            || (mode == BX_SER_MODE_VIRTUAL_MODEM)
+        );
 
 #ifdef BX_SER_WIN32
         if (!winsock_init) {
@@ -510,16 +565,16 @@
             BX_PANIC(("com%d: bind() or listen() failed (host:%s, port:%d)",i+1, host, port));
             continue;
           } else {
-            fprintf(stderr,"com%d: waiting for client to connect (host:%s, port:%d)\n",i+1, host, port);
-            SOCKET client;
-            if ((client = ::accept(socket, NULL, 0)) < 0) {
-              BX_PANIC(("com%d: accept() failed (host:%s, port:%d)",i+1, host, port));
-              continue;
-            } else {
-              fprintf(stderr,"client connected\n");
-              closesocket(socket);
-              socket = client;
+            /* Set non blocking */
+            nonb |= O_NONBLOCK;
+            if ((res = fcntl(socket, F_GETFL, 0)) == -1) {
+                BX_PANIC(("com%d: fcntl(socket, F_GETFL) failed\n", i+1));
+                continue;
+            } else if (fcntl(socket, F_SETFL, res | nonb) == -1) {
+                BX_PANIC(("com%d: fcntl(socket, F_SETFL, nonb) failed. res: %d\n", i+1, (res | nonb)));
+                continue;
             }
+            BX_SER_THIS s[i].server_socket_id = socket;
           }
         } else if (::connect(socket, (sockaddr *) &sin, sizeof (sin)) < 0) {
           // client mode
@@ -1228,6 +1283,22 @@
       break;
 
     case BX_SER_MCR: /* MODEM control register */
+      /* When DTR goes down, drop socket connection */
+      if ( (new_b0 == 0) && (
+            (BX_SER_THIS s[port].io_mode == BX_SER_MODE_NULL_MODEM)
+         || (BX_SER_THIS s[port].io_mode == BX_SER_MODE_VIRTUAL_MODEM)
+      )) {
+            BX_INFO(("com%d: DTR dropped", port+1));
+            if (BX_SER_THIS s[port].modem_status.dcd == 1) {
+                BX_INFO(("com%d: DCD was up, bringing down and closing socket", port+1));
+                closesocket(BX_SER_THIS s[port].socket_id);
+                BX_SER_THIS s[port].socket_id = BX_SER_THIS s[port].server_socket_id;
+                BX_SER_THIS s[port].modem_status.dcd = 0;
+                BX_SER_THIS s[port].modem_status.delta_dcd = 1;
+                BX_SER_THIS s[port].ms_ipending = 1;
+                raise_interrupt(port, BX_SER_INT_MODSTAT);
+            }
+      }
       if ((BX_SER_THIS s[port].io_mode == BX_SER_MODE_MOUSE) &&
           ((BX_SER_THIS s[port].line_cntl.wordlen_sel == 2) ||
            (BX_SER_THIS s[port].line_cntl.wordlen_sel == 3))) {
@@ -1347,7 +1418,7 @@
           BX_SER_THIS s[port].modem_status.cts = 1;
           BX_SER_THIS s[port].modem_status.dsr = 1;
           BX_SER_THIS s[port].modem_status.ri  = 0;
-          BX_SER_THIS s[port].modem_status.dcd = 0;
+          /* BX_SER_THIS s[port].modem_status.dcd = 0; */
         }
       }
       break;
@@ -1378,7 +1449,7 @@
   if (BX_SER_THIS s[port].fifo_cntl.enable) {
     if (BX_SER_THIS s[port].rx_fifo_end == 16) {
       if (!BX_SER_THIS s[port].modem_cntl.local_loopback) {
-        BX_ERROR(("com%d: receive FIFO overflow", port+1));
+        BX_ERROR(("com%d: receive FIFO overflow, char: %d", port+1, data));
       }
       BX_SER_THIS s[port].line_status.overrun_error = 1;
       raise_interrupt(port, BX_SER_INT_RXLSTAT);
@@ -1412,10 +1483,11 @@
       BX_ERROR(("com%d: overrun error", port+1));
       BX_SER_THIS s[port].line_status.overrun_error = 1;
       raise_interrupt(port, BX_SER_INT_RXLSTAT);
+    } else {
+      BX_SER_THIS s[port].rxbuffer = data;
+      BX_SER_THIS s[port].line_status.rxdata_ready = 1;
+      raise_interrupt(port, BX_SER_INT_RXDATA);
     }
-    BX_SER_THIS s[port].rxbuffer = data;
-    BX_SER_THIS s[port].line_status.rxdata_ready = 1;
-    raise_interrupt(port, BX_SER_INT_RXDATA);
   }
 }
 
@@ -1429,6 +1501,16 @@
 void bx_serial_c::tx_timer(void)
 {
   bool gen_int = 0;
+  bool at_error = 0;
+  bool at_silent = 0;
+  char *s;
+  char *v;
+  timespec thetime;
+  Bit64u mstime;
+
+  clock_gettime(CLOCK_MONOTONIC, &thetime);
+  mstime = (Bit64u)(thetime.tv_sec*1000)+(Bit64u)(thetime.tv_nsec/1000000);
+
   Bit8u port = (Bit8u)bx_pc_system.triggeredTimerParam();
 
   switch (BX_SER_THIS s[port].io_mode) {
@@ -1465,6 +1547,198 @@
     case BX_SER_MODE_MOUSE:
       BX_INFO(("com%d: write to mouse ignored: 0x%02x", port+1, BX_SER_THIS s[port].tsrbuffer));
       break;
+    case BX_SER_MODE_VIRTUAL_MODEM:
+      if (BX_SER_THIS s[port].virtual_modem.echo) {
+        rx_fifo_enq(port, BX_SER_THIS s[port].tsrbuffer);
+      }
+      if (!BX_SER_THIS s[port].modem_status.dcd) {
+      /* Handle AT commands sent to the virtual modem */
+      /* This is messy AF, probably needs its own function */
+      if (BX_SER_THIS s[port].tsrbuffer == BX_SER_THIS s[port].virtual_modem.cr_char) {
+        if (
+               (BX_SER_THIS s[port].virtual_modem.at_cmd_buf[0] != 'A')
+            && (BX_SER_THIS s[port].virtual_modem.at_cmd_buf[1] != 'T')
+        ) {
+            BX_SER_THIS s[port].virtual_modem.at_cmd_buf_idx = 0;
+            BX_SER_THIS s[port].virtual_modem.at_cmd_buf[0] = 0;
+            break;
+        }
+        at_error = 0; /* Respond OK by default */
+        at_silent = 0;
+        BX_INFO(("com%d: AT command: %s",
+            port+1,
+            BX_SER_THIS s[port].virtual_modem.at_cmd_buf
+        ));
+        for (int i = 2 ; i < BX_SER_THIS s[port].virtual_modem.at_cmd_buf_idx; i++) {
+            switch(BX_SER_THIS s[port].virtual_modem.at_cmd_buf[i]) {
+                case ' ': /* Spaces are ignored */
+                    break;
+                case 'A':
+                    if (BX_SER_THIS s[port].virtual_modem.rings_sent) {
+                        BX_INFO(("com%d: Got ATA from the VM, holding to simulate modem connect time.", port+1));
+                        if (BX_SER_THIS s[port].virtual_modem.verbose) {
+                            sprintf(BX_SER_THIS s[port].virtual_modem.tx_buffer,
+                                "%s%c%c",
+                                "CONNECT 14400",
+                                BX_SER_THIS s[port].virtual_modem.cr_char,
+                                BX_SER_THIS s[port].virtual_modem.lf_char
+                            );
+                        } else {
+                            sprintf(BX_SER_THIS s[port].virtual_modem.tx_buffer,
+                                "%s%c",
+                                "14",
+                                BX_SER_THIS s[port].virtual_modem.cr_char
+                            );
+                        }
+                        BX_SER_THIS s[port].virtual_modem.tx_buffer_send_time = mstime + (BX_SER_THIS s[port].virtual_modem.carrier_detect_time * 100);
+                        BX_SER_THIS s[port].virtual_modem.tx_buffer_idx = 0;
+                        BX_SER_THIS s[port].virtual_modem.at_cmd_buf_idx = 0;
+                        BX_SER_THIS s[port].virtual_modem.at_cmd_buf[0] = 0;
+                        BX_SER_THIS s[port].virtual_modem.off_hook = 1;
+                        BX_SER_THIS s[port].virtual_modem.rings_sent = 0;
+                        BX_SER_THIS s[port].virtual_modem.last_ring_time = 0;
+                        BX_SER_THIS s[port].modem_status.dcd = 1;
+                        BX_SER_THIS s[port].modem_status.delta_dcd = 1;
+                        BX_SER_THIS s[port].modem_status.ri = 0;
+                        BX_SER_THIS s[port].modem_status.ri_trailedge = 0;
+                        BX_SER_THIS s[port].ms_ipending = 1;
+                        raise_interrupt(port, BX_SER_INT_MODSTAT);
+                        at_silent = 1;
+                    }
+                    while (isdigit(BX_SER_THIS s[port].virtual_modem.at_cmd_buf[i+1])) {
+                        i++;
+                    }
+                    break;
+                case 'D':
+                    /* Outbound dialing not supported */
+                    BX_INFO(("com%d: Sending ERROR in reply to ATD command.", port+1));
+                    at_error = 1;
+                    i++;
+                    break;
+                case 'E':
+                    if (   (BX_SER_THIS s[port].virtual_modem.at_cmd_buf[i+1] != '0')
+                        && (BX_SER_THIS s[port].virtual_modem.at_cmd_buf[i+1] != '1')
+                    ) {
+                        break;
+                    }
+                    i++;
+                    BX_SER_THIS s[port].virtual_modem.echo = BX_SER_THIS s[port].virtual_modem.at_cmd_buf[i] - '0';
+                    break;
+                case 'H':
+                    if (   (BX_SER_THIS s[port].virtual_modem.at_cmd_buf[i+1] != '0')
+                        && (BX_SER_THIS s[port].virtual_modem.at_cmd_buf[i+1] != '1')
+                    ) {
+                        BX_SER_THIS s[port].virtual_modem.off_hook = 0;
+                        BX_INFO(("com%d: Virtual modem is now on the hook.",port+1));
+                        break;
+                    }
+                    i++;
+                    BX_SER_THIS s[port].virtual_modem.off_hook = BX_SER_THIS s[port].virtual_modem.at_cmd_buf[i] - '0';
+                    if (!BX_SER_THIS s[port].virtual_modem.off_hook) {
+                        BX_INFO(("com%d: Virtual modem is now on the hook.",port+1));
+                    }
+                    break;
+                case 'S': /* S registers */
+                    i++; /* first digit of register number */
+                    s = &BX_SER_THIS s[port].virtual_modem.at_cmd_buf[i];
+                    while (isdigit(BX_SER_THIS s[port].virtual_modem.at_cmd_buf[i+1])) {
+                        i++;
+                    }
+                    i++; /* equals sign */
+                    i++; /* first digit of value */
+                    v = &BX_SER_THIS s[port].virtual_modem.at_cmd_buf[i];
+                    while (isdigit(BX_SER_THIS s[port].virtual_modem.at_cmd_buf[i+1])) {
+                        i++;
+                    }
+                    switch(atoi(s)) {
+                        case 0:
+                            BX_SER_THIS s[port].virtual_modem.auto_answer = atoi(v);
+                            break;
+                        case 9:
+                            BX_SER_THIS s[port].virtual_modem.carrier_detect_time = atoi(v);
+                            break;
+                        case 18:
+                            BX_SER_THIS s[port].virtual_modem.at_delay = atoi(v);
+                            break;
+                        default:
+                            break;
+                    }
+                    break;
+                case 'V':
+                    if (   (BX_SER_THIS s[port].virtual_modem.at_cmd_buf[i+1] != '0')
+                        && (BX_SER_THIS s[port].virtual_modem.at_cmd_buf[i+1] != '1')
+                    ) {
+                        break;
+                    }
+                    i++;
+                    BX_SER_THIS s[port].virtual_modem.verbose = BX_SER_THIS s[port].virtual_modem.at_cmd_buf[i] - '0';
+                    break;
+                default: /* Covers ATZ, et al */
+                    if (BX_SER_THIS s[port].virtual_modem.at_cmd_buf[i] == '&') {
+                        i++;
+                    }
+                    while (isdigit(BX_SER_THIS s[port].virtual_modem.at_cmd_buf[i+1])) {
+                        i++;
+                    }
+                    break;
+            }
+        }
+        if (!at_silent) {
+            if (at_error) {
+                if (BX_SER_THIS s[port].virtual_modem.verbose) {
+                    sprintf(BX_SER_THIS s[port].virtual_modem.tx_buffer,
+                        "%s%c%c",
+                        "ERROR",
+                        BX_SER_THIS s[port].virtual_modem.cr_char,
+                        BX_SER_THIS s[port].virtual_modem.lf_char
+                    );
+                } else {
+                    sprintf(BX_SER_THIS s[port].virtual_modem.tx_buffer,
+                        "%s%c",
+                        "1",
+                        BX_SER_THIS s[port].virtual_modem.cr_char
+                    );
+                }
+            } else {
+                if (BX_SER_THIS s[port].virtual_modem.verbose) {
+                    sprintf(BX_SER_THIS s[port].virtual_modem.tx_buffer,
+                        "%s%c%c",
+                        "OK",
+                        BX_SER_THIS s[port].virtual_modem.cr_char,
+                        BX_SER_THIS s[port].virtual_modem.lf_char
+                    );
+                } else {
+                    sprintf(BX_SER_THIS s[port].virtual_modem.tx_buffer,
+                        "%s%c",
+                        "0",
+                        BX_SER_THIS s[port].virtual_modem.cr_char
+                    );
+                }
+            }
+            BX_SER_THIS s[port].virtual_modem.tx_buffer_send_time = mstime + (BX_SER_THIS s[port].virtual_modem.at_delay * 100);
+        }
+        BX_SER_THIS s[port].modem_status.delta_cts = 1;
+        BX_SER_THIS s[port].modem_status.delta_dsr = 1;
+        BX_SER_THIS s[port].modem_status.cts = 1;
+        BX_SER_THIS s[port].modem_status.dsr = 1;
+        BX_SER_THIS s[port].virtual_modem.at_cmd_buf_idx = 0;
+        BX_SER_THIS s[port].virtual_modem.at_cmd_buf[0] = 0;
+        break;
+      }
+      /* Add to buffer and move on */
+      BX_SER_THIS s[port].virtual_modem.at_cmd_buf[BX_SER_THIS s[port].virtual_modem.at_cmd_buf_idx] = BX_SER_THIS s[port].tsrbuffer;
+      BX_SER_THIS s[port].virtual_modem.at_cmd_buf_idx++;
+      BX_SER_THIS s[port].virtual_modem.at_cmd_buf[BX_SER_THIS s[port].virtual_modem.at_cmd_buf_idx] = 0;
+      /* AT command at 40 chars? flush everything */
+      if (BX_SER_THIS s[port].virtual_modem.at_cmd_buf_idx > 39) {
+        BX_SER_THIS s[port].virtual_modem.at_cmd_buf_idx = 0;
+        BX_SER_THIS s[port].virtual_modem.at_cmd_buf[0] = 0;
+      }
+      break;
+      } /* Active connection, purposefully fall through here */
+    case BX_SER_MODE_NULL_MODEM:
+      if (BX_SER_THIS s[port].modem_status.dcd != 1)
+        break;
     case BX_SER_MODE_SOCKET_CLIENT:
     case BX_SER_MODE_SOCKET_SERVER:
       if (BX_SER_THIS s[port].socket_id >= 0) {
@@ -1523,6 +1797,12 @@
   bool data_ready = 0;
   int db_usec = BX_SER_THIS s[port].databyte_usec;
   unsigned char chbuf = 0;
+  int newfd;
+  timespec thetime;
+  Bit64u mstime;
+
+  clock_gettime(CLOCK_MONOTONIC, &thetime);
+  mstime = (Bit64u)(thetime.tv_sec*1000)+(Bit64u)(thetime.tv_nsec/1000000);
 
   if (BX_SER_THIS s[port].io_mode == BX_SER_MODE_TERM) {
 #if BX_HAVE_SELECT && defined(SERIAL_ENABLE)
@@ -1541,6 +1821,298 @@
   if ((BX_SER_THIS s[port].line_status.rxdata_ready == 0) ||
       (BX_SER_THIS s[port].fifo_cntl.enable)) {
     switch (BX_SER_THIS s[port].io_mode) {
+      case BX_SER_MODE_NULL_MODEM:
+      case BX_SER_MODE_VIRTUAL_MODEM:
+        if (BX_SER_THIS s[port].modem_status.dcd == 1) {
+            if (   BX_SER_THIS s[port].virtual_modem.tx_buffer[0]
+                && (mstime > BX_SER_THIS s[port].virtual_modem.tx_buffer_send_time)
+            ) {
+                if (BX_SER_THIS s[port].fifo_cntl.enable) {
+                    BX_INFO(("com%d: Sending buffered data that was held via FIFO.", port+1));
+                    for (int i=0;BX_SER_THIS s[port].virtual_modem.tx_buffer[i] != 0;i++) {
+                        rx_fifo_enq(port, BX_SER_THIS s[port].virtual_modem.tx_buffer[i]);
+                    }
+                } else { /* no FIFO */
+                    BX_INFO(("com%d: Sending buffered data that was held without FIFO.", port+1));
+                    if (BX_SER_THIS s[port].line_status.rxdata_ready == 1) {
+                        BX_INFO(("com%d: Virtual modem data ready. UART is not.", port+1));
+                        break;
+                    }
+                    BX_SER_THIS s[port].rxbuffer =
+                        BX_SER_THIS s[port].virtual_modem.tx_buffer[
+                            BX_SER_THIS s[port].virtual_modem.tx_buffer_idx
+                        ];
+                    BX_SER_THIS s[port].line_status.rxdata_ready = 1;
+                    raise_interrupt(port, BX_SER_INT_RXDATA);
+                    BX_SER_THIS s[port].virtual_modem.tx_buffer_idx++;
+                    if (BX_SER_THIS s[port].virtual_modem.tx_buffer[
+                        BX_SER_THIS s[port].virtual_modem.tx_buffer_idx] != 0) {
+                        break;
+                    }
+                }
+                BX_SER_THIS s[port].virtual_modem.tx_buffer_idx = 0;
+                BX_SER_THIS s[port].virtual_modem.tx_buffer[0] = 0;
+                break;
+            }
+            if ((newfd = accept(BX_SER_THIS s[port].server_socket_id, NULL, NULL)) > 0) {
+                write(newfd,
+                    "Sorry, the virtual modem is busy with another call.\r\n",
+                    47
+                );
+                closesocket(newfd);
+                break;
+            }
+            if (BX_SER_THIS s[port].line_status.rxdata_ready == 0) {
+              tval.tv_sec  = 0;
+              tval.tv_usec = 0;
+              FD_ZERO(&fds);
+              SOCKET socketid = BX_SER_THIS s[port].socket_id;
+              if (socketid >= 0) {
+                FD_SET(socketid, &fds);
+                if (recv(socketid, (char*) &chbuf, 1, MSG_PEEK | MSG_DONTWAIT) == 0) {
+                    BX_INFO(("com%d: connection reset by peer", port+1));
+                    closesocket(BX_SER_THIS s[port].socket_id);
+                    BX_SER_THIS s[port].modem_status.dcd = 0;
+                    BX_SER_THIS s[port].modem_status.delta_dcd = 1;
+                    BX_SER_THIS s[port].socket_id = BX_SER_THIS s[port].server_socket_id;
+                    BX_SER_THIS s[port].virtual_modem.last_ring_time = 0;
+                    BX_SER_THIS s[port].virtual_modem.rings_sent = 0;
+                    BX_SER_THIS s[port].virtual_modem.tx_buffer_send_time = mstime;
+                    if (BX_SER_THIS s[port].io_mode == BX_SER_MODE_VIRTUAL_MODEM) {
+                        if (BX_SER_THIS s[port].virtual_modem.verbose) {
+                            sprintf(BX_SER_THIS s[port].virtual_modem.tx_buffer,
+                                "%s%c%c",
+                                "NO CARRIER",
+                                BX_SER_THIS s[port].virtual_modem.cr_char,
+                                BX_SER_THIS s[port].virtual_modem.lf_char
+                            );
+                        } else {
+                            sprintf(BX_SER_THIS s[port].virtual_modem.tx_buffer,
+                                "%s%c",
+                                "3",
+                                BX_SER_THIS s[port].virtual_modem.cr_char
+                            );
+                        }
+                    }
+                    BX_SER_THIS s[port].ms_ipending = 1;
+    /*                BX_SER_THIS s[port].int_enable.modstat_enable = 1; */
+                    raise_interrupt(port, BX_SER_INT_MODSTAT);
+                    break;
+                }
+
+                if (select((int)(socketid+1), &fds, NULL, NULL, &tval) == 1) {
+                  ssize_t bytes = (ssize_t)
+                  ::recv(socketid, (char*) &chbuf, 1, 0);
+                  if (bytes > 0) {
+                    BX_DEBUG(("com%d: read byte [0x%02x]", port+1, chbuf));
+                    data_ready = 1;
+                  }
+                }
+              }
+            }
+            break;
+        }
+
+        if (   BX_SER_THIS s[port].virtual_modem.tx_buffer[0]
+            && (mstime > BX_SER_THIS s[port].virtual_modem.tx_buffer_send_time)
+        ) {
+            if (BX_SER_THIS s[port].fifo_cntl.enable) {
+                for (int i=0; BX_SER_THIS s[port].virtual_modem.tx_buffer[i] != 0 ; i++) {
+                    rx_fifo_enq(port, BX_SER_THIS s[port].virtual_modem.tx_buffer[i]);
+                }
+            } else { /* no FIFO */
+                if (BX_SER_THIS s[port].line_status.rxdata_ready == 1) {
+                    BX_INFO(("com%d: Virtual modem data ready. UART is not.", port+1));
+                    break;
+                }
+                BX_SER_THIS s[port].rxbuffer =
+                    BX_SER_THIS s[port].virtual_modem.tx_buffer[
+                        BX_SER_THIS s[port].virtual_modem.tx_buffer_idx
+                    ];
+                BX_SER_THIS s[port].line_status.rxdata_ready = 1;
+                raise_interrupt(port, BX_SER_INT_RXDATA);
+                if (BX_SER_THIS s[port].virtual_modem.tx_buffer[
+                    BX_SER_THIS s[port].virtual_modem.tx_buffer_idx++] != 0) {
+                    break;
+                }
+            }
+            BX_INFO(("Resetting virtual modem's TX buffer"));
+            BX_SER_THIS s[port].virtual_modem.tx_buffer_idx = 0;
+            BX_SER_THIS s[port].virtual_modem.tx_buffer[0] = 0;
+            break;
+        }
+        if (  BX_SER_THIS s[port].virtual_modem.last_ring_time
+            && recv(BX_SER_THIS s[port].socket_id, (char*) &chbuf, 1, MSG_PEEK | MSG_DONTWAIT) == 0) {
+                BX_INFO(("com%d: While RING, connection reset by peer", port+1));
+                closesocket(BX_SER_THIS s[port].socket_id);
+/*                if (BX_SER_THIS s[port].virtual_modem.verbose) {
+                    sprintf(BX_SER_THIS s[port].virtual_modem.tx_buffer,
+                        "%s%c%c",
+                        "NO CARRIER",
+                        BX_SER_THIS s[port].virtual_modem.cr_char,
+                        BX_SER_THIS s[port].virtual_modem.lf_char
+                    );
+                } else {
+                    sprintf(BX_SER_THIS s[port].virtual_modem.tx_buffer,
+                        "%s%c",
+                        "3",
+                        BX_SER_THIS s[port].virtual_modem.cr_char
+                    );
+                } */
+                BX_SER_THIS s[port].virtual_modem.tx_buffer_send_time = mstime;
+                BX_SER_THIS s[port].modem_status.ri = 0;
+                BX_SER_THIS s[port].modem_status.ri_trailedge = 0;
+                BX_SER_THIS s[port].virtual_modem.last_ring_time = 0;
+                BX_SER_THIS s[port].virtual_modem.rings_sent = 0;
+                BX_SER_THIS s[port].virtual_modem.off_hook = 0;
+                BX_SER_THIS s[port].ms_ipending = 1;
+/*                BX_SER_THIS s[port].int_enable.modstat_enable = 1; */
+                raise_interrupt(port, BX_SER_INT_MODSTAT);
+                break;
+        }
+        if (BX_SER_THIS s[port].virtual_modem.last_ring_time) {
+            if (BX_SER_THIS s[port].virtual_modem.rings_sent >= 5) {
+                BX_INFO(("com%d: Timeout after five RINGs sent. Disconnecting.", port+1));
+/*                if (BX_SER_THIS s[port].virtual_modem.verbose) {
+                    sprintf(BX_SER_THIS s[port].virtual_modem.tx_buffer,
+                        "%s%c%c",
+                        "NO CARRIER",
+                        BX_SER_THIS s[port].virtual_modem.cr_char,
+                        BX_SER_THIS s[port].virtual_modem.lf_char
+                    );
+                } else {
+                    sprintf(BX_SER_THIS s[port].virtual_modem.tx_buffer,
+                        "%s%c",
+                        "3",
+                        BX_SER_THIS s[port].virtual_modem.cr_char
+                    );
+                } */
+                closesocket(BX_SER_THIS s[port].socket_id);
+                BX_SER_THIS s[port].virtual_modem.tx_buffer_send_time = mstime;
+                BX_SER_THIS s[port].modem_status.ri = 0;
+                BX_SER_THIS s[port].modem_status.ri_trailedge = 0;
+                BX_SER_THIS s[port].virtual_modem.last_ring_time = 0;
+                BX_SER_THIS s[port].virtual_modem.rings_sent = 0;
+                BX_SER_THIS s[port].virtual_modem.off_hook = 0;
+                BX_SER_THIS s[port].ms_ipending = 1;
+/*                BX_SER_THIS s[port].int_enable.modstat_enable = 1; */
+                raise_interrupt(port, BX_SER_INT_MODSTAT);
+                break;
+            }
+            if ( (BX_SER_THIS s[port].virtual_modem.last_ring_time + 3000) < mstime) {
+                BX_INFO(("com%d: Three seconds since last RING, sending another.", port+1));
+                if (BX_SER_THIS s[port].virtual_modem.verbose) {
+                    sprintf(BX_SER_THIS s[port].virtual_modem.tx_buffer,
+                        "%s%c%c",
+                        "RING",
+                        BX_SER_THIS s[port].virtual_modem.cr_char,
+                        BX_SER_THIS s[port].virtual_modem.lf_char
+                    );
+                } else {
+                    sprintf(BX_SER_THIS s[port].virtual_modem.tx_buffer,
+                        "%s%c",
+                        "2",
+                        BX_SER_THIS s[port].virtual_modem.cr_char
+                    );
+                }
+                BX_SER_THIS s[port].virtual_modem.tx_buffer_send_time = mstime;
+                BX_SER_THIS s[port].modem_status.ri_trailedge = 1;
+                BX_SER_THIS s[port].modem_status.ri = 1;
+                BX_SER_THIS s[port].virtual_modem.last_ring_time = mstime;
+                BX_SER_THIS s[port].virtual_modem.rings_sent++;
+                raise_interrupt(port, BX_SER_INT_MODSTAT);
+                break;
+            }
+        }
+        if ((newfd = accept(BX_SER_THIS s[port].server_socket_id, NULL, NULL)) > 0) {
+          if (BX_SER_THIS s[port].io_mode == BX_SER_MODE_VIRTUAL_MODEM) {
+              if (BX_SER_THIS s[port].virtual_modem.off_hook) {
+                write(newfd,
+                    "The virtual modem is off the hook and isn't accepting calls.\r\n",
+                    62
+                );
+                closesocket(newfd);
+                break;
+              }
+              if (BX_SER_THIS s[port].virtual_modem.rings_sent != 0) {
+                write(newfd,
+                    "The virtual modem is already ringing from another caller.\r\n",
+                    59
+                );
+                closesocket(newfd);
+                break;
+              }
+              write(newfd, "The virtual modem is ringing, please wait...\r\n", 47);
+          }
+          if (BX_SER_THIS s[port].modem_status.dcd == 1) {
+              write(newfd, "Sorry, this line is busy with another call.\r\n", 46);
+              closesocket(newfd);
+              break;
+          }
+          BX_SER_THIS s[port].socket_id = newfd;
+          if (   (BX_SER_THIS s[port].io_mode == BX_SER_MODE_NULL_MODEM)
+              || BX_SER_THIS s[port].virtual_modem.auto_answer
+          ) {
+                if (BX_SER_THIS s[port].io_mode == BX_SER_MODE_VIRTUAL_MODEM) {
+                    if (BX_SER_THIS s[port].virtual_modem.verbose) {
+                        sprintf(BX_SER_THIS s[port].virtual_modem.tx_buffer,
+                            "%s%c%c",
+                            "CONNECT 14400",
+                            BX_SER_THIS s[port].virtual_modem.cr_char,
+                            BX_SER_THIS s[port].virtual_modem.lf_char
+                        );
+                    } else {
+                        sprintf(BX_SER_THIS s[port].virtual_modem.tx_buffer,
+                            "%s%c",
+                            "14",
+                            BX_SER_THIS s[port].virtual_modem.cr_char
+                        );
+                    }
+                }
+                BX_SER_THIS s[port].virtual_modem.tx_buffer_send_time = mstime;
+                BX_SER_THIS s[port].virtual_modem.tx_buffer_idx = 0;
+                BX_SER_THIS s[port].virtual_modem.at_cmd_buf_idx = 0;
+                BX_SER_THIS s[port].virtual_modem.at_cmd_buf[0] = 0;
+                BX_SER_THIS s[port].virtual_modem.off_hook = 1;
+                BX_SER_THIS s[port].virtual_modem.rings_sent = 0;
+                BX_SER_THIS s[port].virtual_modem.last_ring_time = 0;
+                BX_SER_THIS s[port].modem_status.dcd = 1;
+                BX_SER_THIS s[port].modem_status.delta_dcd = 1;
+                BX_SER_THIS s[port].modem_status.ri = 0;
+                BX_SER_THIS s[port].modem_status.ri_trailedge = 0;
+                BX_SER_THIS s[port].ms_ipending = 1;
+/*                BX_SER_THIS s[port].int_enable.modstat_enable = 1; */
+                raise_interrupt(port, BX_SER_INT_MODSTAT);
+                break;
+          }
+          if (BX_SER_THIS s[port].virtual_modem.rings_sent == 0) {
+              if (BX_SER_THIS s[port].virtual_modem.verbose) {
+                  sprintf(BX_SER_THIS s[port].virtual_modem.tx_buffer,
+                      "%s%c%c",
+                      "RING",
+                      BX_SER_THIS s[port].virtual_modem.cr_char,
+                      BX_SER_THIS s[port].virtual_modem.lf_char
+                  );
+              } else {
+                  sprintf(BX_SER_THIS s[port].virtual_modem.tx_buffer,
+                      "%s%c",
+                      "2",
+                      BX_SER_THIS s[port].virtual_modem.cr_char
+                  );
+              }
+              BX_INFO(("com%d: accepted new TCP connection, sent RING", port+1));
+              BX_SER_THIS s[port].virtual_modem.tx_buffer_send_time = mstime;
+              BX_SER_THIS s[port].virtual_modem.last_ring_time = mstime;
+              BX_SER_THIS s[port].virtual_modem.rings_sent = 1;
+              BX_SER_THIS s[port].virtual_modem.off_hook = 1;
+              BX_SER_THIS s[port].modem_status.ri_trailedge = 1;
+              BX_SER_THIS s[port].modem_status.ri = 1;
+              BX_SER_THIS s[port].ms_ipending = 1;
+/*              BX_SER_THIS s[port].int_enable.modstat_enable = 1; */
+              raise_interrupt(port, BX_SER_INT_MODSTAT);
+          }
+        }
+        break;
       case BX_SER_MODE_SOCKET_CLIENT:
       case BX_SER_MODE_SOCKET_SERVER:
 #if BX_HAVE_SELECT && defined(SERIAL_ENABLE)
--- bochs/iodev/serial.h	2022-05-06 07:31:21.132121231 +0700
+++ bochs/iodev/serial.h	2022-05-06 08:25:33.764280076 +0700
@@ -72,6 +72,9 @@
 #define BX_SER_MODE_SOCKET_SERVER 6
 #define BX_SER_MODE_PIPE_CLIENT   7
 #define BX_SER_MODE_PIPE_SERVER   8
+#define BX_SER_MODE_NULL_MODEM    9
+#define BX_SER_MODE_VIRTUAL_MODEM 10
+#define BX_SER_MODE_SOCKET_SERVER_WAITING 11
 
 enum {
   BX_SER_INT_IER,
@@ -115,6 +118,7 @@
   int io_mode;
   int tty_id;
   SOCKET socket_id;
+  SOCKET server_socket_id;
   FILE *output;
   bx_param_string_c *file;
 #ifdef BX_SER_WIN32
@@ -190,6 +194,42 @@
     bool    ri;                 /* RI input value */
     bool    dcd;                /* DCD input value */
   } modem_status;
+  /* Virtual Modem */
+  struct {
+    /* Regular AT toggles */
+    bool echo;                 /* ATE */
+    bool off_hook;             /* ATH - 0 = On hook, 1 = Off hook */
+    bool quiet;                /* ATQ */
+    bool verbose;              /* ATV - 0 = Numeric, 1 = English */
+
+    /* "S" Registers */
+    Bit8u auto_answer;            /* S0 - Number of rings, 0 = never */
+    Bit8u rings_sent;             /* S1 - Read-only, stores # of rings */
+    Bit8u esc_char;               /* S2 - ASCII value, default: 43 ("+") */
+    Bit8u cr_char;                /* S3 - ASCII value, default: 13 (CR) */
+    Bit8u lf_char;                /* S4 - ASCII value, default: 10 (LF) */
+    Bit8u bs_char;                /* S5 - ASCII value, default: 8 (BS) */
+    Bit8u blind_dial_wait;        /* S6 */
+    Bit8u dial_carrier_wait;      /* S7 */
+    Bit8u comma_pause_time;       /* S8 */
+    Bit8u carrier_detect_time;    /* S9 - Delay between ATA and CONNECT 1/10 sec */
+    Bit8u no_carrier_hangup_time; /* S10 - Tenths of a second */
+    Bit8u escape_guard_time;      /* S12 - Fiftieths of a second */
+    Bit8u at_delay;               /* S18 - Delay between AT and OK 1/10 sec */
+    Bit8u delay_to_dtr;           /* S25 */
+    Bit8u rts_to_cts_delay;       /* S26 - Hundredths of a second */
+    Bit8u inactivity_timer;       /* S30 - Tenths of a second */
+
+    char tx_buffer[40];     /* Used in both command and data modes */
+    Bit8u tx_buffer_idx;
+    Bit8u tx_buffer_length;
+    Bit64u tx_buffer_send_time;   /* Used to delay buffer responses in tandem with S18 */
+
+    Bit64u last_ring_time;       /* Used to space rings in a real-world fashion */
+
+    int at_cmd_buf_idx;
+    char at_cmd_buf[40];
+  } virtual_modem;
 
   Bit8u  scratch;       /* Scratch Register (r/w) */
   Bit8u  tsrbuffer;     /* transmit shift register (internal) */
--- bochs/gui/gui.cc	2021-06-07 18:30:08.022810000 +0700
+++ bochs/gui/gui.cc	2022-05-06 07:31:33.227696824 +0700
@@ -1020,7 +1020,7 @@
 void bx_gui_c::show_ips(Bit32u ips_count)
 {
 #if BX_SHOW_IPS
-  BX_INFO(("ips = %3.3fM", ips_count / 1000000.0));
+  BX_DEBUG(("ips = %3.3fM", ips_count / 1000000.0));
 #endif
 }
 
