Index: trunk/src/VBox/ValidationKit/tests/serial/Makefile.kmk
===================================================================
--- trunk/src/VBox/ValidationKit/tests/serial/Makefile.kmk	(revision 70771)
+++ trunk/src/VBox/ValidationKit/tests/serial/Makefile.kmk	(revision 70796)
@@ -33,5 +33,6 @@
 ValidationKitTestsSerial_INST = $(INST_VALIDATIONKIT)tests/serial/
 ValidationKitTestsSerial_EXEC_SOURCES := \
-	$(PATH_SUB_CURRENT)/tdSerial1.py
+	$(PATH_SUB_CURRENT)/tdSerial1.py \
+	$(PATH_SUB_CURRENT)/loopback.py
 
 VBOX_VALIDATIONKIT_PYTHON_SOURCES += $(ValidationKitTestsSerial_EXEC_SOURCES)
Index: trunk/src/VBox/ValidationKit/tests/serial/loopback.py
===================================================================
--- trunk/src/VBox/ValidationKit/tests/serial/loopback.py	(revision 70796)
+++ trunk/src/VBox/ValidationKit/tests/serial/loopback.py	(revision 70796)
@@ -0,0 +1,215 @@
+# -*- coding: utf-8 -*-
+# $Id$
+
+"""
+VirtualBox Validation Kit - Serial loopback module.
+"""
+
+__copyright__ = \
+"""
+Copyright (C) 2018 Oracle Corporation
+
+This file is part of VirtualBox Open Source Edition (OSE), as
+available from http://www.virtualbox.org. This file is free software;
+you can redistribute it and/or modify it under the terms of the GNU
+General Public License (GPL) as published by the Free Software
+Foundation, in version 2 as it comes in the "COPYING" file of the
+VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+
+The contents of this file may alternatively be used under the terms
+of the Common Development and Distribution License Version 1.0
+(CDDL) only, as it comes in the "COPYING.CDDL" file of the
+VirtualBox OSE distribution, in which case the provisions of the
+CDDL are applicable instead of those of the GPL.
+
+You may elect to license modified versions of this file under the
+terms and conditions of either the GPL or the CDDL or both.
+"""
+__version__ = "$Revision$"
+
+# Standard Python imports.
+#import os;
+import socket;
+import threading;
+
+
+g_ksLoopbackTcpServ         = 'TcpServ';
+g_ksLoopbackTcpClient       = 'TcpClient';
+g_ksLoopbackNamedPipeServ   = 'NamedPipeServ';
+g_ksLoopbackNamedPipeClient = 'NamedPipeClient';
+
+class SerialLoopbackTcpServ(object):
+    """
+    Handler for a server TCP style connection.
+    """
+    def __init__(self, sLocation, iTimeout):
+        sHost, sPort = sLocation.split(':');
+        self.oConn = None;
+        self.oSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM);
+        self.oSock.settimeout(iTimeout);
+        self.oSock.bind((sHost, int(sPort)));
+        self.oSock.listen(1);
+        self.iTimeout = iTimeout;
+
+    def __del__(self):
+        if self.oConn is not None:
+            self.oConn.close();
+        self.oSock.close();
+
+    def pumpIo(self):
+        """
+        Main I/O pumping routine.
+        """
+        try:
+            if self.oConn is None:
+                oConn, _ = self.oSock.accept();
+                self.oConn = oConn;
+            else:
+                abData = self.oConn.recv(1024);
+                if abData is not None:
+                    self.oConn.send(abData);
+        except:
+            pass;
+
+class SerialLoopbackTcpClient(object):
+    """
+    Handler for a client TCP style connection.
+    """
+    def __init__(self, sLocation, iTimeout):
+        sHost, sPort = sLocation.split(':');
+        self.oConn = socket.socket(socket.AF_INET, socket.SOCK_STREAM);
+        self.oConn.connect((sHost, int(sPort)));
+        self.oConn.settimeout(iTimeout);
+        self.iTimeout = iTimeout;
+
+    def __del__(self):
+        self.oConn.close();
+
+    def pumpIo(self):
+        """
+        Main I/O pumping routine.
+        """
+        try:
+            abData = self.oConn.recv(1024);
+            if abData is not None:
+                self.oConn.send(abData);
+        except:
+            pass;
+
+class SerialLoopbackNamedPipeServ(object):
+    """
+    Handler for a named pipe server style connection.
+    """
+    def __init__(self, sLocation, iTimeout):
+        self.oConn = None;
+        self.oSock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM);
+        self.oSock.settimeout(iTimeout);
+        self.oSock.bind(sLocation);
+        self.oSock.listen(1);
+        self.iTimeout = iTimeout;
+
+    def __del__(self):
+        if self.oConn is not None:
+            self.oConn.close();
+        self.oSock.close();
+
+    def pumpIo(self):
+        """
+        Main I/O pumping routine.
+        """
+        try:
+            if self.oConn is None:
+                oConn, _ = self.oSock.accept();
+                self.oConn = oConn;
+            else:
+                abData = self.oConn.recv(1024);
+                if abData is not None:
+                    self.oConn.send(abData);
+        except:
+            pass;
+
+class SerialLoopbackNamedPipeClient(object):
+    """
+    Handler for a named pipe client style connection.
+    """
+    def __init__(self, sLocation, iTimeout):
+        self.oConn = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM);
+        self.oConn.connect(sLocation);
+        self.oConn.settimeout(iTimeout);
+        self.iTimeout = iTimeout;
+
+    def __del__(self):
+        self.oConn.close();
+
+    def pumpIo(self):
+        """
+        Main I/O pumping routine.
+        """
+        try:
+            abData = self.oConn.recv(1024);
+            if abData is not None:
+                self.oConn.send(abData);
+        except:
+            pass;
+
+class SerialLoopback(object):
+    """
+    Serial port loopback module working with TCP and named pipes.
+    """
+
+    def __init__(self, sType, sLocation):
+        self.fShutdown = False;
+        self.sType     = sType;
+        self.sLocation = sLocation;
+        self.oLock     = threading.Lock();
+        self.oThread   = threading.Thread(target=self.threadWorker, args=(), name=('SerLoopback'));
+
+        if sType == g_ksLoopbackTcpServ:
+            self.oIoPumper = SerialLoopbackTcpServ(sLocation, 0.5);
+            self.oThread.start();
+        elif sType == g_ksLoopbackNamedPipeServ:
+            self.oIoPumper = SerialLoopbackNamedPipeServ(sLocation, 0.5);
+            self.oThread.start();
+
+    def connect(self):
+        """
+        Connects to the server for a client type version.
+        """
+        fRc = True;
+        try:
+            if self.sType == g_ksLoopbackTcpClient:
+                self.oIoPumper = SerialLoopbackTcpClient(self.sLocation, 0.5);
+            elif self.sType == g_ksLoopbackNamedPipeClient:
+                self.oIoPumper = SerialLoopbackNamedPipeClient(self.sLocation, 0.5);
+        except:
+            fRc = False;
+        else:
+            self.oThread.start();
+        return fRc;
+
+    def shutdown(self):
+        """
+        Shutdown any connection and wait for it to become idle.
+        """
+        self.oLock.acquire();
+        self.fShutdown = True;
+        self.oLock.release();
+
+    def isShutdown(self):
+        """
+        Returns whether the I/O pumping thread should shut down.
+        """
+        self.oLock.acquire();
+        fShutdown = self.fShutdown;
+        self.oLock.release();
+
+        return fShutdown;
+
+    def threadWorker(self):
+        """
+        The threaded worker.
+        """
+        while not self.isShutdown():
+            self.oIoPumper.pumpIo();
+
Index: trunk/src/VBox/ValidationKit/tests/serial/tdSerial1.py
===================================================================
--- trunk/src/VBox/ValidationKit/tests/serial/tdSerial1.py	(revision 70771)
+++ trunk/src/VBox/ValidationKit/tests/serial/tdSerial1.py	(revision 70796)
@@ -48,5 +48,7 @@
 from testdriver import reporter;
 from testdriver import vbox;
-
+from testdriver import vboxcon;
+
+import loopback;
 
 class tdSerial1(vbox.TestDriver):
@@ -59,8 +61,10 @@
         self.asRsrcs          = None;
         self.oTestVmSet       = self.oTestVmManager.selectSet(self.oTestVmManager.kfGrpStdSmoke);
-        self.asSerialModesDef = ['RawFile']; # @todo: Add Tcp, TcpServ, Pipe, PipeServ, HostDev
+        self.asSerialModesDef = ['RawFile', 'Tcp', 'TcpServ', 'NamedPipe', 'NamedPipeServ', 'HostDev'];
         self.asSerialModes    = self.asSerialModesDef;
-        self.asSerialTestsDef = ['Write']; # @todo: Add read write and modem line control
+        self.asSerialTestsDef = ['Write', 'ReadWrite'];
         self.asSerialTests    = self.asSerialTestsDef;
+        self.oLoopback        = None;
+        self.sLocation        = None;
 
     #
@@ -132,5 +136,52 @@
         return os.path.join(oTestDrv.sScratchPath, oTestVm.sVmName + sInfix + sRandom + sSuffix);
 
-    def testWrite(self, oSession, oTxsSession, sCom1RawFile, oTestVm):
+    def setupSerialMode(self, oSession, oTestVm, sMode):
+        """
+        Sets up the serial mode.
+        """
+        fRc = True;
+        fServer = False;
+        sLocation = None;
+        ePortMode = vboxcon.PortMode_Disconnected;
+        if sMode == 'RawFile':
+            sLocation = self._generateRawPortFilename(self, oTestVm, '-com1-', '.out');
+            ePortMode = vboxcon.PortMode_RawFile;
+        elif sMode == 'Tcp':
+            sLocation = '127.0.0.1:1234';
+            self.oLoopback = loopback.SerialLoopback(loopback.g_ksLoopbackTcpServ, sLocation);
+            ePortMode = vboxcon.PortMode_TCP;
+        elif sMode == 'TcpServ':
+            fServer   = True;
+            sLocation = '1234';
+            ePortMode = vboxcon.PortMode_TCP;
+            self.oLoopback = loopback.SerialLoopback(loopback.g_ksLoopbackTcpClient, '127.0.0.1:1234');
+        elif sMode == 'NamedPipe':
+            sLocation = self._generateRawPortFilename(self, oTestVm, '-com1-', '.out');
+            ePortMode = vboxcon.PortMode_HostPipe;
+            self.oLoopback = loopback.SerialLoopback(loopback.g_ksLoopbackNamedPipeServ, sLocation);
+        elif sMode == 'NamedPipeServ':
+            fServer   = True;
+            sLocation = self._generateRawPortFilename(self, oTestVm, '-com1-', '.out');
+            ePortMode = vboxcon.PortMode_HostPipe;
+            self.oLoopback = loopback.SerialLoopback(loopback.g_ksLoopbackNamedPipeClient, sLocation);
+        elif sMode == 'HostDev':
+            sLocation = '/dev/ttyUSB0';
+            ePortMode = vboxcon.PortMode_HostDevice;
+        else:
+            reporter.log('warning, invalid mode %s given' % (sMode, ));
+            fRc = False;
+
+        if fRc:
+            fRc = oSession.changeSerialPortAttachment(0, ePortMode, sLocation, fServer);
+            if fRc and (sMode == 'TcpServ' or sMode == 'NamedPipeServ'):
+                self.sleep(2); # Fudge to allow the TCP server to get started.
+                fRc = self.oLoopback.connect();
+                if not fRc:
+                    reporter.log('Failed to connect to %s' % (sLocation, ));
+            self.sLocation = sLocation;
+
+        return fRc;
+
+    def testWrite(self, oSession, oTxsSession, oTestVm, sMode):
         """
         Does a simple write test verifying the output.
@@ -149,9 +200,9 @@
         if not fRc:
             reporter.testFailure('Running serial test utility failed');
-        else:
+        elif sMode == 'RawFile':
             # Open serial port and verify
             cLast = 0;
             try:
-                oFile = open(sCom1RawFile, 'rb');
+                oFile = open(self.sLocation, 'rb');
                 sFmt = '=I';
                 cBytes = 4;
@@ -169,4 +220,38 @@
                 reporter.testFailure('Verifying the written data failed');
         reporter.testDone();
+        return fRc;
+
+    def testReadWrite(self, oSession, oTxsSession, oTestVm):
+        """
+        Does a simple write test verifying the output.
+        """
+        _ = oSession;
+
+        reporter.testStart('ReadWrite');
+        tupCmdLine = ('SerialTest', '--tests', 'readwrite', '--txbytes', '1048576', '--device');
+        if oTestVm.isWindows():
+            tupCmdLine += (r'\\.\COM1',);
+        elif oTestVm.isLinux():
+            tupCmdLine += (r'/dev/ttyS0',);
+
+        fRc = self.txsRunTest(oTxsSession, 'SerialTest', 600 * 1000, \
+            '${CDROM}/${OS/ARCH}/SerialTest${EXESUFF}', tupCmdLine);
+        if not fRc:
+            reporter.testFailure('Running serial test utility failed');
+
+        reporter.testDone();
+        return fRc;
+
+    def isModeCompatibleWithTest(self, sMode, sTest):
+        """
+        Returns whether the given port mode and test combination is
+        supported for testing.
+        """
+        if sMode == 'RawFile' and sTest == 'ReadWrite':
+            return False;
+        elif sMode != 'RawFile' and sTest == 'Write':
+            return False;
+
+        return True;
 
     def testOneVmConfig(self, oVM, oTestVm):
@@ -177,8 +262,7 @@
         # Reconfigure the VM
         fRc = True;
-        sCom1RawFile = self._generateRawPortFilename(self, oTestVm, '-com1-', '.out');
         oSession = self.openSession(oVM);
         if oSession is not None:
-            fRc = oSession.setupSerialToRawFile(0, sCom1RawFile)
+            fRc = oSession.enableSerialPort(0);
 
             fRc = fRc and oSession.saveSettings();
@@ -194,7 +278,20 @@
                 self.addTask(oTxsSession);
 
-                for sTest in self.asSerialTests:
-                    if sTest == 'Write':
-                        fRc = self.testWrite(oSession, oTxsSession, sCom1RawFile, oTestVm);
+                for sMode in self.asSerialModes:
+                    reporter.testStart(sMode);
+                    fRc = self.setupSerialMode(oSession, oTestVm, sMode);
+                    if fRc:
+                        for sTest in self.asSerialTests:
+                            # Skip tests which don't work with the current mode.
+                            if self.isModeCompatibleWithTest(sMode, sTest):
+                                if sTest == 'Write':
+                                    fRc = self.testWrite(oSession, oTxsSession, oTestVm, sMode);
+                                if sTest == 'ReadWrite':
+                                    fRc = self.testReadWrite(oSession, oTxsSession, oTestVm);
+                        if self.oLoopback is not None:
+                            self.oLoopback.shutdown();
+                            self.oLoopback = None;
+
+                    reporter.testDone();
 
                 self.removeTask(oTxsSession);
