diff options
Diffstat (limited to 'src/serialport/serialport_wince.cpp')
-rw-r--r-- | src/serialport/serialport_wince.cpp | 475 |
1 files changed, 475 insertions, 0 deletions
diff --git a/src/serialport/serialport_wince.cpp b/src/serialport/serialport_wince.cpp new file mode 100644 index 0000000..7489747 --- /dev/null +++ b/src/serialport/serialport_wince.cpp @@ -0,0 +1,475 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Denis Shienkov <scapig@yandex.ru> +** Copyright (C) 2012 Laszlo Papp <lpapp@kde.org> +** Copyright (C) 2012 Andre Hartmann <aha_1980@gmx.de> +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtSerialPort module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "serialport_win_p.h" + +#include <QtCore/qelapsedtimer.h> + +#include <QtCore/qthread.h> +#include <QtCore/qtimer.h> + +QT_BEGIN_NAMESPACE_SERIALPORT + +class SerialPortPrivate; + +class CommEventNotifier : public QThread +{ + Q_OBJECT +signals: + void eventMask(quint32 mask); + +public: + CommEventNotifier(DWORD mask, SerialPortPrivate *d, QObject *parent) + : QThread(parent), dptr(d), running(true) { + connect(this, SIGNAL(eventMask(quint32)), this, SLOT(processNotification(quint32))); + ::SetCommMask(dptr->descriptor, mask); + } + + virtual ~CommEventNotifier() { + running = false; + ::SetCommMask(dptr->descriptor, 0); + wait(); + } + +protected: + virtual void run() { + DWORD mask = 0; + while (running) { + if (::WaitCommEvent(dptr->descriptor, &mask, FALSE)) { + // Wait until complete the operation changes the port settings, + // see updateDcb(). + dptr->settingsChangeMutex.lock(); + dptr->settingsChangeMutex.unlock(); + emit eventMask(quint32(mask)); + } + } + } + +private slots: + void processNotification(quint32 eventMask) { + if (EV_ERR & eventMask) + dptr->processIoErrors(); + if (EV_RXCHAR &eventMask) + dptr->notifyRead(); + if (EV_TXEMPTY & eventMask) + dptr->notifyWrite(SerialPortPrivateData::WriteChunkSize); + } + +private: + SerialPortPrivate *dptr; + mutable bool running; +}; + +class WaitCommEventBreaker : public QThread +{ + Q_OBJECT +public: + WaitCommEventBreaker(HANDLE descriptor, int timeout, QObject *parent = 0) + : QThread(parent), descriptor(descriptor), timeout(timeout), worked(false) { + start(); + } + + virtual ~WaitCommEventBreaker() { + stop(); + wait(); + } + + void stop() { + exit(0); + } + + bool isWorked() const { + return worked; + } + +protected: + void run() { + QTimer timer; + QObject::connect(&timer, SIGNAL(timeout()), this, SLOT(processTimeout()), Qt::DirectConnection); + timer.start(timeout); + exec(); + worked = true; + } + +private slots: + void processTimeout() { + ::SetCommMask(descriptor, 0); + stop(); + } + +private: + HANDLE descriptor; + int timeout; + mutable bool worked; +}; + +SerialPortPrivate::SerialPortPrivate(SerialPort *q) + : SerialPortPrivateData(q) + , descriptor(INVALID_HANDLE_VALUE) + , flagErrorFromCommEvent(0) + , eventNotifier(0) +{ +} + +bool SerialPortPrivate::open(QIODevice::OpenMode mode) +{ + DWORD desiredAccess = 0; + DWORD eventMask = EV_ERR; + + if (mode & QIODevice::ReadOnly) { + desiredAccess |= GENERIC_READ; + eventMask |= EV_RXCHAR; + } + if (mode & QIODevice::WriteOnly) { + desiredAccess |= GENERIC_WRITE; + eventMask |= EV_TXEMPTY; + } + + descriptor = ::CreateFile(reinterpret_cast<const wchar_t*>(systemLocation.utf16()), + desiredAccess, 0, NULL, OPEN_EXISTING, 0, NULL); + + if (descriptor == INVALID_HANDLE_VALUE) { + portError = decodeSystemError(); + return false; + } + + if (!::GetCommState(descriptor, &restoredDcb)) { + portError = decodeSystemError(); + return false; + } + + currentDcb = restoredDcb; + currentDcb.fBinary = true; + currentDcb.fInX = false; + currentDcb.fOutX = false; + currentDcb.fAbortOnError = false; + currentDcb.fNull = false; + currentDcb.fErrorChar = false; + + if (!updateDcb()) + return false; + + if (!::GetCommTimeouts(descriptor, &restoredCommTimeouts)) { + portError = decodeSystemError(); + return false; + } + + ::memset(¤tCommTimeouts, 0, sizeof(currentCommTimeouts)); + currentCommTimeouts.ReadIntervalTimeout = MAXDWORD; + + if (!updateCommTimeouts()) + return false; + + eventNotifier = new QtAddOn::SerialPort::CommEventNotifier(eventMask, this, q_ptr); + eventNotifier->start(); + + detectDefaultSettings(); + return true; +} + +void SerialPortPrivate::close() +{ + if (eventNotifier) { + eventNotifier->deleteLater(); + eventNotifier = 0; + } + + if (restoreSettingsOnClose) { + ::SetCommState(descriptor, &restoredDcb); + ::SetCommTimeouts(descriptor, &restoredCommTimeouts); + } + + ::CloseHandle(descriptor); + descriptor = INVALID_HANDLE_VALUE; +} + +bool SerialPortPrivate::flush() +{ + return notifyWrite() && ::FlushFileBuffers(descriptor); +} + +qint64 SerialPortPrivate::readFromBuffer(char *data, qint64 maxSize) +{ + if (readBuffer.isEmpty()) + return 0; + + if (maxSize == 1) { + *data = readBuffer.getChar(); + return 1; + } + + const qint64 bytesToRead = qMin(qint64(readBuffer.size()), maxSize); + qint64 readSoFar = 0; + while (readSoFar < bytesToRead) { + const char *ptr = readBuffer.readPointer(); + const int bytesToReadFromThisBlock = qMin(int(bytesToRead - readSoFar), + readBuffer.nextDataBlockSize()); + ::memcpy(data + readSoFar, ptr, bytesToReadFromThisBlock); + readSoFar += bytesToReadFromThisBlock; + readBuffer.free(bytesToReadFromThisBlock); + } + + return readSoFar; +} + +qint64 SerialPortPrivate::writeToBuffer(const char *data, qint64 maxSize) +{ + char *ptr = writeBuffer.reserve(maxSize); + if (maxSize == 1) + *ptr = *data; + else + ::memcpy(ptr, data, maxSize); + + return maxSize; +} + +bool SerialPortPrivate::waitForReadyRead(int msec) +{ + if (!readBuffer.isEmpty()) + return true; + + QElapsedTimer stopWatch; + + stopWatch.start(); + + forever { + bool readyToRead = false; + bool readyToWrite = false; + bool timedOut = false; + if (!waitForReadOrWrite(&readyToRead, &readyToWrite, + true, !writeBuffer.isEmpty(), + timeoutValue(msec, stopWatch.elapsed()), + &timedOut)) { + return false; + } + if (readyToRead) { + if (notifyRead()) + return true; + } + if (readyToWrite) + notifyWrite(WriteChunkSize); + } + return false; +} + +bool SerialPortPrivate::waitForBytesWritten(int msec) +{ + if (writeBuffer.isEmpty()) + return false; + + QElapsedTimer stopWatch; + + stopWatch.start(); + + forever { + bool readyToRead = false; + bool readyToWrite = false; + bool timedOut = false; + if (!waitForReadOrWrite(&readyToRead, &readyToWrite, + true, !writeBuffer.isEmpty(), + timeoutValue(msec, stopWatch.elapsed()), + &timedOut)) { + return false; + } + if (readyToRead) { + if (!notifyRead()) + return false; + } + if (readyToWrite) { + if (notifyWrite(WriteChunkSize)) + return true; + } + } + return false; +} + +bool SerialPortPrivate::notifyRead() +{ + DWORD bytesToRead = (policy == SerialPort::IgnorePolicy) ? ReadChunkSize : 1; + + if (readBufferMaxSize && bytesToRead > (readBufferMaxSize - readBuffer.size())) { + bytesToRead = readBufferMaxSize - readBuffer.size(); + if (bytesToRead == 0) { + // Buffer is full. User must read data from the buffer + // before we can read more from the port. + return false; + } + } + + char *ptr = readBuffer.reserve(bytesToRead); + + DWORD readBytes = 0; + BOOL sucessResult = ::ReadFile(descriptor, ptr, bytesToRead, &readBytes, NULL); + + if (!sucessResult) { + readBuffer.truncate(bytesToRead); + return false; + } + + readBuffer.truncate(readBytes); + + // Process emulate policy. + if (flagErrorFromCommEvent) { + flagErrorFromCommEvent = false; + + switch (policy) { + case SerialPort::SkipPolicy: + readBuffer.getChar(); + return true; + case SerialPort::PassZeroPolicy: + readBuffer.getChar(); + readBuffer.putChar('\0'); + break; + case SerialPort::StopReceivingPolicy: + // FIXME: Maybe need disable read notifier? + break; + default: + break; + } + } + + if (readBytes > 0) + emit q_ptr->readyRead(); + + return true; +} + +bool SerialPortPrivate::notifyWrite(int maxSize) +{ + int nextSize = qMin(writeBuffer.nextDataBlockSize(), maxSize); + + const char *ptr = writeBuffer.readPointer(); + + DWORD bytesWritten = 0; + if (!::WriteFile(descriptor, ptr, nextSize, &bytesWritten, NULL)) + return false; + + writeBuffer.free(bytesWritten); + + if (bytesWritten > 0) + emit q_ptr->bytesWritten(bytesWritten); + + return true; +} + +bool SerialPortPrivate::waitForReadOrWrite(bool *selectForRead, bool *selectForWrite, + bool checkRead, bool checkWrite, + int msecs, bool *timedOut) +{ + // FIXME: Here the situation is not properly handled with zero timeout: + // breaker can work out before you call a method WaitCommEvent() + // and so it will loop forever! + WaitCommEventBreaker breaker(descriptor, qMax(msecs, 0)); + ::WaitCommEvent(descriptor, &eventMask, NULL); + breaker.stop(); + + if (breaker.isWorked()) + *timedOut = true; + + if (!breaker.isWorked()) { + if (checkRead) { + Q_ASSERT(selectForRead); + *selectForRead = eventMask & EV_RXCHAR; + } + if (checkWrite) { + Q_ASSERT(selectForWrite); + *selectForWrite = eventMask & EV_TXEMPTY; + } + + return true; + } + + return false; +} + +bool SerialPortPrivate::updateDcb() +{ + QMutexLocker locker(&settingsChangeMutex); + + DWORD eventMask = 0; + // Save the event mask + if (!::GetCommMask(descriptor, &eventMask)) + return false; + + // Break event notifier from WaitCommEvent + ::SetCommMask(descriptor, 0); + // Change parameters + bool ret = ::SetCommState(descriptor, ¤tDcb); + if (!ret) + portError = decodeSystemError(); + // Restore the event mask + ::SetCommMask(descriptor, eventMask); + + return ret; +} + +bool SerialPortPrivate::updateCommTimeouts() +{ + if (!::SetCommTimeouts(descriptor, ¤tCommTimeouts)) { + portError = decodeSystemError(); + return false; + } + return true; +} + +static const QLatin1String defaultPathPostfix(":"); + +QString SerialPortPrivate::portNameToSystemLocation(const QString &port) +{ + QString ret = port; + if (!ret.contains(defaultPathPostfix)) + ret.append(defaultPathPostfix); + return ret; +} + +QString SerialPortPrivate::portNameFromSystemLocation(const QString &location) +{ + QString ret = location; + if (ret.contains(defaultPathPostfix)) + ret.remove(defaultPathPostfix); + return ret; +} + +#include "serialport_wince.moc" + +QT_END_NAMESPACE_SERIALPORT |