diff --git a/src/shared/symbianutils/tcftrkdevice.cpp b/src/shared/symbianutils/tcftrkdevice.cpp
index 202fb18a687a162e3150b4af644fbf2b06a37419..17d0fd8ee379e4537bd95109b492e3e23aeb94c5 100644
--- a/src/shared/symbianutils/tcftrkdevice.cpp
+++ b/src/shared/symbianutils/tcftrkdevice.cpp
@@ -29,6 +29,7 @@
 
 #include "tcftrkdevice.h"
 #include "json.h"
+#include "trkutils.h"
 
 #include <QtNetwork/QAbstractSocket>
 #include <QtCore/QDebug>
@@ -40,7 +41,62 @@
 
 enum { debug = 0 };
 
-static const char messageTerminatorC[] = "\003\001";
+static const char wlanMessageTerminatorC[] = "\003\001";
+
+// Serial Ping: 0xfc,0x1f
+static const char serialPingC[] = "\xfc\x1f";
+// Serial Pong: 0xfc,0xf1, followed by version info
+static const char serialPongC[] = "\xfc\xf1";
+
+static const char locatorAnswerC[] = "E\0Locator\0Hello\0[\"Locator\"]";
+
+static const unsigned serialChunkLength = 0x400;  // 1K max USB router
+static const int maxSerialMessageLength = 0x10000; // give chunking scheme
+
+// Create USB router frame
+static inline void encodeSerialFrame(const QByteArray &data, QByteArray *target)
+{
+    target->append(char(0x01));
+    target->append(char(0x92)); // CODA serial message ID
+    appendShort(target, ushort(data.size()), trk::BigEndian);
+    target->append(data);
+}
+
+// Split in chunks of 1K according to CODA protocol chunking
+static inline QByteArray encodeUsbSerialMessage(const QByteArray &dataIn)
+{
+    static const int chunkSize = serialChunkLength -  2; // 2 Header bytes
+    const int size = dataIn.size();
+    QByteArray frame;
+    // Do we need to split?
+    if (size < chunkSize) {  // Nope, all happy.
+        frame.reserve(size + 4);
+        encodeSerialFrame(dataIn, &frame);
+        return frame;
+    }
+    // Split.
+    unsigned chunkCount = size / chunkSize;
+    if (size % chunkSize)
+        chunkCount++;
+    if (debug)
+        qDebug("Serial: Splitting message of %d bytes into %u chunks of %d", size, chunkCount, chunkSize);
+
+    frame.reserve((4 + serialChunkLength) * chunkCount);
+    int pos = 0;
+    for (unsigned c = chunkCount - 1; pos < size ; c--) {
+        QByteArray chunk; // chunk with long message start/continuation code
+        chunk.reserve(serialChunkLength);
+        chunk.append(pos ? char(0) : char(0xfe));
+        chunk.append(char(static_cast<unsigned char>(c))); // Avoid any signedness issues.
+        const int chunkEnd = qMin(pos + chunkSize, size);
+        chunk.append(dataIn.mid(pos, chunkEnd - pos));
+        encodeSerialFrame(chunk, &frame);
+        pos = chunkEnd;
+    }
+    if (debug > 1)
+        qDebug("Serial chunked:\n%s", qPrintable(tcftrk::formatData(frame)));
+    return frame;
+}
 
 namespace tcftrk {
 // ------------- TcfTrkCommandError
@@ -272,11 +328,12 @@ struct TcfTrkDevicePrivate {
     TokenWrittenMessageMap m_writtenMessages;
     QVector<QByteArray> m_registerNames;
     QVector<QByteArray> m_fakeGetMRegisterValues;
+    bool m_serialFrame;
 };
 
 TcfTrkDevicePrivate::TcfTrkDevicePrivate() :
-    m_messageTerminator(messageTerminatorC),
-    m_verbose(0), m_token(0)
+    m_messageTerminator(wlanMessageTerminatorC),
+    m_verbose(0), m_token(0), m_serialFrame(false)
 {
 }
 
@@ -380,7 +437,12 @@ void TcfTrkDevice::slotDeviceSocketStateChanged()
 
 static inline QString debugMessage(QByteArray  message, const char *prefix = 0)
 {
-    message.replace('\0', '|');
+    const bool isBinary = !message.isEmpty() && message.at(0) < 0;
+    if (isBinary) {
+        message = message.toHex(); // Some serial special message
+    } else {
+        message.replace('\0', '|');
+    }
     const QString messageS = QString::fromLatin1(message);
     return prefix ?
             (QLatin1String(prefix) + messageS) :  messageS;
@@ -388,30 +450,101 @@ static inline QString debugMessage(QByteArray  message, const char *prefix = 0)
 
 void TcfTrkDevice::slotDeviceReadyRead()
 {
-    d->m_readBuffer += d->m_device->readAll();
+    const QByteArray newData = d->m_device->readAll();
+    d->m_readBuffer += newData;
+    if (debug)
+        qDebug("ReadBuffer: %s", qPrintable(trk::stringFromArray(newData)));
+    if (d->m_serialFrame) {
+        deviceReadyReadSerial();
+    } else {
+        deviceReadyReadWLAN();
+    }
+}
+
+// Find a serial header in input stream '0x1', '0x92', 'lenH', 'lenL'
+// and return message position and size.
+static inline QPair<int, int> findSerialHeader(const QByteArray &in)
+{
+    const int size = in.size();
+    const char header1 = 0x1;
+    const char header2 = char(0x92);
+    // Header should in theory always be at beginning of
+    // buffer. Warn if  there are bogus data in-between.
+    for (int pos = 0; pos < size; ) {
+        if (pos + 4 < size && in.at(pos) == header1 &&  in.at(pos + 1) == header2) {
+            const int length = trk::extractShort(in.constData() + 2);
+            return QPair<int, int>(pos + 4, length);
+        }
+        // Find next
+        pos = in.indexOf(header1, pos + 1);
+        qWarning("Bogus data received on serial line: %s\n"
+                 "Frame Header at: %d", qPrintable(trk::stringFromArray(in)), pos);
+        if (pos < 0)
+           break;
+    }
+    return QPair<int, int>(-1, -1);
+}
+
+void TcfTrkDevice::deviceReadyReadSerial()
+{
+    do {
+        // Extract message (pos,len)
+        const QPair<int, int> messagePos = findSerialHeader(d->m_readBuffer);
+        if (messagePos.first < 0)
+            break;
+        // Do we have the complete message?
+        const int messageEnd = messagePos.first + messagePos.second;
+        if (messageEnd > d->m_readBuffer.size())
+            break;
+        const QByteArray message = d->m_readBuffer.mid(messagePos.first, messagePos.second);
+        // Is thing a ping/pong response
+        if (debug > 1)
+            qDebug("Serial message: at %d (%d bytes) of %d: %s",
+                   messagePos.first, messagePos.second, d->m_readBuffer.size(),
+                   qPrintable(trk::stringFromArray(message)));
+        if (message.startsWith(serialPongC)) {
+            const QString version = QString::fromLatin1(message.mid(sizeof(serialPongC) -  1));
+            emitLogMessage(QString::fromLatin1("Serial connection from '%1'").arg(version));
+            emit serialPong(version);
+            // Answer with locator.
+            writeMessage(QByteArray(locatorAnswerC, sizeof(locatorAnswerC)));
+        } else {
+            processMessage(message);
+        }
+        d->m_readBuffer.remove(0, messageEnd);
+    } while (d->m_readBuffer.isEmpty());
+    checkSendQueue(); // Send off further messages
+}
+
+void TcfTrkDevice::deviceReadyReadWLAN()
+{
     // Take complete message off front of readbuffer.
     do {
-        const int messageEndPos = d->m_readBuffer.indexOf(d->m_messageTerminator);        
+        const int messageEndPos = d->m_readBuffer.indexOf(d->m_messageTerminator);
         if (messageEndPos == -1)
             break;
         if (messageEndPos == 0) {
             // TCF TRK 4.0.5 emits empty messages on errors.
             emitLogMessage(QString::fromLatin1("An empty TCF TRK message has been received."));
         } else {
-            const QByteArray message = d->m_readBuffer.left(messageEndPos);
-            if (debug)
-                qDebug("Read %d bytes:\n%s", message.size(), qPrintable(formatData(message)));
-            if (const int errorCode = parseMessage(message)) {
-                emitLogMessage(QString::fromLatin1("Parse error %1 : %2").
-                               arg(errorCode).arg(debugMessage(message)));
-                if (debug)
-                    qDebug("Parse error %d for %d bytes:\n%s", errorCode,
-                           message.size(), qPrintable(formatData(message)));
-            }
+            processMessage(d->m_readBuffer.left(messageEndPos));
         }
         d->m_readBuffer.remove(0, messageEndPos + d->m_messageTerminator.size());
     } while (!d->m_readBuffer.isEmpty());
-    checkSendQueue(); // Send off further message
+    checkSendQueue(); // Send off further messages
+}
+
+void TcfTrkDevice::processMessage(const QByteArray &message)
+{
+    if (debug)
+        qDebug("Read %d bytes:\n%s", message.size(), qPrintable(formatData(message)));
+    if (const int errorCode = parseMessage(message)) {
+        emitLogMessage(QString::fromLatin1("Parse error %1 : %2").
+                       arg(errorCode).arg(debugMessage(message)));
+        if (debug)
+            qDebug("Parse error %d for %d bytes:\n%s", errorCode,
+                   message.size(), qPrintable(formatData(message)));
+    }
 }
 
 // Split \0-terminated message into tokens, skipping the initial type character
@@ -551,8 +684,6 @@ int TcfTrkDevice::parseTcfCommandReply(char type, const QVector<QByteArray> &tok
     return 0;
 }
 
-static const char locatorAnswerC[] = "E\0Locator\0Hello\0[\"Locator\"]";
-
 int TcfTrkDevice::parseTcfEvent(const QVector<QByteArray> &tokens)
 {
     // Event: Ignore the periodical heartbeat event, answer 'Hello',
@@ -572,9 +703,10 @@ int TcfTrkDevice::parseTcfEvent(const QVector<QByteArray> &tokens)
     // Parse known events, emit signals
     QScopedPointer<TcfTrkEvent> knownEvent(TcfTrkEvent::parseEvent(service, tokens.at(1), values));
     if (!knownEvent.isNull()) {
-        // Answer hello event.
+        // Answer hello event (WLAN)
         if (knownEvent->type() == TcfTrkEvent::LocatorHello)
-            writeMessage(QByteArray(locatorAnswerC, sizeof(locatorAnswerC)));
+            if (!d->m_serialFrame)
+                writeMessage(QByteArray(locatorAnswerC, sizeof(locatorAnswerC)));
         emit tcfEvent(*knownEvent);
     }
     emit genericTcfEvent(service, tokens.at(1), values);
@@ -600,6 +732,16 @@ unsigned TcfTrkDevice::verbose() const
     return d->m_verbose;
 }
 
+bool TcfTrkDevice::serialFrame() const
+{
+    return d->m_serialFrame;
+}
+
+void TcfTrkDevice::setSerialFrame(bool s)
+{
+    d->m_serialFrame = s;
+}
+
 void TcfTrkDevice::setVerbose(unsigned v)
 {
     d->m_verbose = v;
@@ -625,6 +767,17 @@ bool TcfTrkDevice::checkOpen()
     return true;
 }
 
+void TcfTrkDevice::sendSerialPing()
+{
+    if (!checkOpen())
+        return;
+
+    setSerialFrame(true);
+    writeMessage(QByteArray(serialPingC, qstrlen(serialPingC)), false);
+    if (d->m_verbose)
+        emitLogMessage(QLatin1String("Ping..."));
+}
+
 void TcfTrkDevice::sendTcfTrkMessage(MessageType mt, Services service, const char *command,
                                      const char *commandParameters, // may contain '\0'
                                      int commandParametersLength,
@@ -673,18 +826,29 @@ void TcfTrkDevice::sendTcfTrkMessage(MessageType mt, Services service, const cha
 }
 
 // Enclose in message frame and write.
-void TcfTrkDevice::writeMessage(QByteArray data)
+void TcfTrkDevice::writeMessage(QByteArray data, bool ensureTerminating0)
 {
     if (!checkOpen())
         return;
 
+    if (d->m_serialFrame && data.size() > maxSerialMessageLength) {
+        qCritical("Attempt to send large message (%d bytes) exceeding the "
+                  "limit of %d bytes over serial channel. Skipping.",
+                  data.size(), maxSerialMessageLength);
+        return;
+    }
+
     if (d->m_verbose)
         emitLogMessage(debugMessage(data, "TCF <-"));
 
     // Ensure \0-termination which easily gets lost in QByteArray CT.
-    if (!data.endsWith('\0'))
+    if (ensureTerminating0 && !data.endsWith('\0'))
         data.append('\0');
-    data += d->m_messageTerminator;
+    if (d->m_serialFrame) {
+        data = encodeUsbSerialMessage(data);
+    } else {
+        data += d->m_messageTerminator;
+    }
 
     if (debug > 1)
         qDebug("Writing:\n%s", qPrintable(formatData(data)));
diff --git a/src/shared/symbianutils/tcftrkdevice.h b/src/shared/symbianutils/tcftrkdevice.h
index d8cfa14f453f00bc77fa44658c9d876c0bd2a0d9..4ab5fbbda01784c2c1fee28fd907c6244f2d6e38 100644
--- a/src/shared/symbianutils/tcftrkdevice.h
+++ b/src/shared/symbianutils/tcftrkdevice.h
@@ -132,11 +132,20 @@ http://dev.eclipse.org/svnroot/dsdp/org.eclipse.tm.tcf/trunk/docs/TCF%20Services
  * single commands. As soon as 'Registers::getm' is natively supported, all code
  * related to 'FakeRegisterGetm' should be removed. The workaround requires that
  * the register name is known.
-*/
+ * CODA notes:
+ * - Commands are accepted only after receiving the Locator Hello event
+ * - Serial communication initiation sequence:
+ *     Send serial ping from host sendSerialPing() -> receive pong response with
+ *     version information -> Send Locator Hello Event -> Receive Locator Hello Event
+ *     -> Commands are accepted.
+ * - WLAN communication initiation sequence:
+ *     Receive Locator Hello Event from CODA -> Commands are accepted.
+ */
 
 class SYMBIANUTILS_EXPORT TcfTrkDevice : public QObject
 {
     Q_PROPERTY(unsigned verbose READ verbose WRITE setVerbose)
+    Q_PROPERTY(bool serialFrame READ serialFrame WRITE setSerialFrame)
     Q_OBJECT
 public:
     // Flags for FileSystem:open
@@ -163,6 +172,8 @@ public:
     virtual ~TcfTrkDevice();
 
     unsigned verbose() const;
+    bool serialFrame() const;
+    void setSerialFrame(bool);
 
     // Mapping of register names to indices for multi-requests.
     // Register names can be retrieved via 'Registers:getChildren' (requires
@@ -174,6 +185,9 @@ public:
     IODevicePtr takeDevice();
     void setDevice(const IODevicePtr &dp);
 
+    // Serial Only: Initiate communication. Will emit serialPong() signal with version.
+    void sendSerialPing();
+
     // Send with parameters from string (which may contain '\0').
     void sendTcfTrkMessage(MessageType mt, Services service,
                            const char *command,
@@ -338,6 +352,7 @@ public:
 signals:
     void genericTcfEvent(int service, const QByteArray &name, const QVector<tcftrk::JsonValue> &value);
     void tcfEvent(const tcftrk::TcfTrkEvent &knownEvent);
+    void serialPong(const QString &codaVersion);
 
     void logMessage(const QString &);
     void error(const QString &);
@@ -351,11 +366,15 @@ private slots:
     void slotDeviceReadyRead();
 
 private:
+    void deviceReadyReadSerial();
+    void deviceReadyReadWLAN();
+
     bool checkOpen();
     void checkSendQueue();
-    void writeMessage(QByteArray data);
+    void writeMessage(QByteArray data, bool ensureTerminating0 = true);
     void emitLogMessage(const QString &);
-    int parseMessage(const QByteArray &);
+    inline int parseMessage(const QByteArray &);
+    void processMessage(const QByteArray &message);
     int parseTcfCommandReply(char type, const QVector<QByteArray> &tokens);
     int parseTcfEvent(const QVector<QByteArray> &tokens);
     // Send with parameters from string (which may contain '\0').
diff --git a/tests/tools/codaclient/codaclient.pro b/tests/tools/codaclient/codaclient.pro
index 0744400300aa9ba6b8b40975cae559b2349fb2d2..3c579b4342dbae01dbb276ade84936e8c39a9ad3 100644
--- a/tests/tools/codaclient/codaclient.pro
+++ b/tests/tools/codaclient/codaclient.pro
@@ -1,5 +1,8 @@
 DEFINES += SYMBIANUTILS_INCLUDE_PRI
+
+include(../../../qtcreator.pri)
 include(../../../src/shared/symbianutils/symbianutils.pri)
+# include(../../../src/libs/3rdparty/qextserialport/qextserialport.pri)
 
 QT       += core gui network
 TARGET = codaclient
diff --git a/tests/tools/codaclient/codaclientapplication.cpp b/tests/tools/codaclient/codaclientapplication.cpp
index 7b81df976a03ac1e5dbaea09288b39a9098f357f..aa12dd010a97ec62304f080f90356e06c59d2df7 100644
--- a/tests/tools/codaclient/codaclientapplication.cpp
+++ b/tests/tools/codaclient/codaclientapplication.cpp
@@ -29,6 +29,10 @@
 
 #include "codaclientapplication.h"
 
+#ifdef HAS_SERIALPORT
+#    include <qextserialport/qextserialport.h>
+#endif
+
 #include "tcftrkdevice.h"
 #include <QtNetwork/QTcpSocket>
 #include <QtCore/QFile>
@@ -255,31 +259,27 @@ bool CodaClientApplication::start()
     switch (m_mode) {
     case Launch: {
         const QString args = m_launchArgs.join(QString(QLatin1Char(' ')));
-        std::printf("Launching 0x%x '%s '%s' on %s:%hu (debug: %d)\n",
+        std::printf("Launching 0x%x '%s '%s' (debug: %d)\n",
                     m_launchUID, qPrintable(m_launchBinary),
-                    qPrintable(args), qPrintable(m_address), m_port,
-                    m_launchDebug);
+                    qPrintable(args), m_launchDebug);
     }
         break;
     case Install:
-        std::printf("Installing '%s' to '%s' on %s:%hu\n",
-                    qPrintable(m_installSisFile), qPrintable(m_installTargetDrive),
-                    qPrintable(m_address), m_port);
+        std::printf("Installing '%s' to '%s'\n",
+                    qPrintable(m_installSisFile), qPrintable(m_installTargetDrive));
         break;
     case Put:
-        std::printf("Copying '%s' to '%s' on %s:%hu in chunks of %lluKB\n",
+        std::printf("Copying '%s' to '%s' in chunks of %lluKB\n",
                     qPrintable(m_putLocalFile), qPrintable(m_putRemoteFile),
-                    qPrintable(m_address), m_port, m_putChunkSize / 1024);
+                    m_putChunkSize / 1024);
         break;
     case Stat:
-        std::printf("Retrieving attributes of '%s' from %s:%hu\n",
-                    qPrintable(m_statRemoteFile), qPrintable(m_address), m_port);
+        std::printf("Retrieving attributes of '%s'\n", qPrintable(m_statRemoteFile));
         break;
     case Invalid:
         break;
     }
     // Start connection
-    const QSharedPointer<QTcpSocket> tcfTrkSocket(new QTcpSocket);
     m_trkDevice.reset(new tcftrk::TcfTrkDevice);
     m_trkDevice->setVerbose(m_verbose);
     connect(m_trkDevice.data(), SIGNAL(error(QString)),
@@ -288,9 +288,48 @@ bool CodaClientApplication::start()
         this, SLOT(slotTrkLogMessage(QString)));
     connect(m_trkDevice.data(), SIGNAL(tcfEvent(tcftrk::TcfTrkEvent)),
         this, SLOT(slotTcftrkEvent(tcftrk::TcfTrkEvent)));
-    m_trkDevice->setDevice(tcfTrkSocket);
-    tcfTrkSocket->connectToHost(m_address, m_port);
-    std::printf("Connecting...\n");
+    if (m_address.startsWith(QLatin1String("/dev"))
+            || m_address.startsWith(QLatin1String("com"), Qt::CaseInsensitive)
+            || m_address.startsWith(QLatin1Char('\\'))) {
+#ifdef HAS_SERIALPORT
+        // Serial
+#ifdef Q_OS_WIN
+        const QString fullPort = QextSerialPort::fullPortNameWin(m_address);
+#else
+        const QString fullPort = m_address;
+#endif
+        const QSharedPointer<QextSerialPort>
+                serialPort(new QextSerialPort(fullPort, QextSerialPort::EventDriven));
+        std::printf("Opening port %s...\n", qPrintable(fullPort));
+
+        // Magic USB serial parameters
+        serialPort->setTimeout(2000);
+        serialPort->setBaudRate(BAUD115200);
+        serialPort->setFlowControl(FLOW_OFF);
+        serialPort->setParity(PAR_NONE);
+        serialPort->setDataBits(DATA_8);
+        serialPort->setStopBits(STOP_1);
+
+        m_trkDevice->setSerialFrame(true);
+        m_trkDevice->setDevice(serialPort); // Grab all data from start
+        if (!serialPort->open(QIODevice::ReadWrite|QIODevice::Unbuffered)) {
+            std::fprintf(stderr, "Cannot open port: %s", qPrintable(serialPort->errorString()));
+            return false;
+        }
+        // Initiate communication
+        m_trkDevice->sendSerialPing();
+        serialPort->flush();
+#else
+        std::fprintf(stderr, "Not implemented\n");
+        return false;
+#endif
+    } else {
+        // TCP/IP
+        const QSharedPointer<QTcpSocket> tcfTrkSocket(new QTcpSocket);
+        m_trkDevice->setDevice(tcfTrkSocket);
+        tcfTrkSocket->connectToHost(m_address, m_port);
+        std::printf("Connecting to %s:%hu...\n", qPrintable(m_address), m_port);
+    }
     return true;
 }
 
@@ -399,8 +438,7 @@ void CodaClientApplication::handleFileSystemFStat(const tcftrk::TcfTrkCommandRes
     } else {
         std::fprintf(stderr, "FStat failed: %s\n", qPrintable(result.toString()));
     }
-    m_trkDevice->sendFileSystemCloseCommand(tcftrk::TcfTrkCallback(this, &CodaClientApplication::handleFileSystemClose),
-                                           m_remoteFileHandle);
+    closeRemoteFile();
 }
 
 void CodaClientApplication::handleFileSystemClose(const tcftrk::TcfTrkCommandResult &result)
@@ -490,12 +528,14 @@ void CodaClientApplication::doExit(int ex)
 
     if (!m_trkDevice.isNull()) {
         const QSharedPointer<QIODevice> dev = m_trkDevice->device();
-        if (QAbstractSocket *socket = qobject_cast<QAbstractSocket *>(dev.data())) {
-            if (socket->state() == QAbstractSocket::ConnectedState)
-                socket->disconnectFromHost();
-        } else {
-            if (dev->isOpen())
-                dev->close();
+        if (!dev.isNull()) {
+            if (QAbstractSocket *socket = qobject_cast<QAbstractSocket *>(dev.data())) {
+                if (socket->state() == QAbstractSocket::ConnectedState)
+                    socket->disconnectFromHost();
+            } else {
+                if (dev->isOpen())
+                    dev->close();
+            }
         }
     }
     std::printf("Exiting (%d)\n", ex);