diff options
author | Denis Shienkov <denis.shienkov@gmail.com> | 2012-11-01 21:23:04 +0400 |
---|---|---|
committer | Denis Shienkov <denis.shienkov@gmail.com> | 2012-11-05 13:57:20 +0100 |
commit | db46db74eb0b1afb6a2ff34712b419e20a17f49a (patch) | |
tree | 5756528ef5d7ad6538d997e5d1900d50d288fe6e /src/serialport/serialport_unix.cpp | |
parent | eca1a6d30a98861cb811cb2faf8d01334e60dd7c (diff) | |
download | qtserialport-db46db74eb0b1afb6a2ff34712b419e20a17f49a.tar.gz |
Updating a project tree for compilation fixing
For optimization and build correction for Qt4/Qt5 there was a need for
unification and modification of a tree of projects.
The previous structure of the project didn't meet the changed requirements
for Qt5 that led to errors of build and complication of possibility of
correction of errors of build.
It is necessary to note the main changes:
* Removed not used /modules direcrory with all content, because it is
autogenerated.
* Moved all contents from /src directory to /src/serialport directory. It
was necessary for simplification of the qmake commands at building
for Qt5 (in particular, there is no need to fill out a module name since
it is the name of directory /serialport).
* Changed the exported macro defines Q_ADDON_SERIALPORT_EXPORT to
Q_SERIALPORT_EXPORT and QT_ADDON_SERIALPORT_LIB to QT_SERIALPORT_LIB. It
was necessary for fixing compilation error for unknown reason on Windows
and Qt5 when used MSVC compiler.
Checked on Windows and Gnu/Linux, also this patch fixes bug:
Task-number: QTPLAYGROUND-5
Change-Id: Idc9ba98115d5961a22ae307c0e4034a56f3223b5
Reviewed-by: Denis Shienkov <denis.shienkov@gmail.com>
Diffstat (limited to 'src/serialport/serialport_unix.cpp')
-rw-r--r-- | src/serialport/serialport_unix.cpp | 1327 |
1 files changed, 1327 insertions, 0 deletions
diff --git a/src/serialport/serialport_unix.cpp b/src/serialport/serialport_unix.cpp new file mode 100644 index 0000000..4bb314b --- /dev/null +++ b/src/serialport/serialport_unix.cpp @@ -0,0 +1,1327 @@ +/**************************************************************************** +** +** 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_unix_p.h" +#include "ttylocker_unix_p.h" + +#include <errno.h> +#include <sys/time.h> +#include <sys/ioctl.h> +#include <fcntl.h> +#include <unistd.h> + +#ifdef Q_OS_MAC +#if defined (MAC_OS_X_VERSION_10_4) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_4) +#include <IOKit/serial/ioss.h> +#endif +#endif + +#include <QtCore/qelapsedtimer.h> + +#include <QtCore/qsocketnotifier.h> + +QT_BEGIN_NAMESPACE_SERIALPORT + +class ReadNotifier : public QSocketNotifier +{ +public: + ReadNotifier(SerialPortPrivate *d, QObject *parent) + : QSocketNotifier(d->descriptor, QSocketNotifier::Read, parent) + , dptr(d) + {} + +protected: + virtual bool event(QEvent *e) { + bool ret = QSocketNotifier::event(e); + if (ret) + dptr->readNotification(); + return ret; + } + +private: + SerialPortPrivate *dptr; +}; + +class WriteNotifier : public QSocketNotifier +{ +public: + WriteNotifier(SerialPortPrivate *d, QObject *parent) + : QSocketNotifier(d->descriptor, QSocketNotifier::Write, parent) + , dptr(d) + {} + +protected: + virtual bool event(QEvent *e) { + bool ret = QSocketNotifier::event(e); + if (ret) + dptr->writeNotification(SerialPortPrivateData::WriteChunkSize); + return ret; + } + +private: + SerialPortPrivate *dptr; +}; + +class ExceptionNotifier : public QSocketNotifier +{ +public: + ExceptionNotifier(SerialPortPrivate *d, QObject *parent) + : QSocketNotifier(d->descriptor, QSocketNotifier::Exception, parent) + , dptr(d) + {} + +protected: + virtual bool event(QEvent *e) { + bool ret = QSocketNotifier::event(e); + if (ret) + dptr->exceptionNotification(); + return ret; + } + +private: + SerialPortPrivate *dptr; +}; + +SerialPortPrivate::SerialPortPrivate(SerialPort *q) + : SerialPortPrivateData(q) + , descriptor(-1) + , isCustomRateSupported(false) + , readNotifier(0) + , writeNotifier(0) + , exceptionNotifier(0) + , readPortNotifierCalled(false) + , readPortNotifierState(false) + , readPortNotifierStateSet(false) + , emittedReadyRead(false) + , emittedBytesWritten(false) +{ +} + +bool SerialPortPrivate::open(QIODevice::OpenMode mode) +{ + QByteArray portName = portNameFromSystemLocation(systemLocation).toLocal8Bit(); + const char *ptr = portName.constData(); + + bool byCurrPid = false; + if (TtyLocker::isLocked(ptr, &byCurrPid)) { + portError = SerialPort::PermissionDeniedError; + return false; + } + + int flags = O_NOCTTY | O_NONBLOCK; + + switch (mode & QIODevice::ReadWrite) { + case QIODevice::WriteOnly: + flags |= O_WRONLY; + break; + case QIODevice::ReadWrite: + flags |= O_RDWR; + break; + default: + flags |= O_RDONLY; + break; + } + + descriptor = ::open(systemLocation.toLocal8Bit().constData(), flags); + + if (descriptor == -1) { + portError = decodeSystemError(); + return false; + } + + ::fcntl(descriptor, F_SETFL, FNDELAY); + + TtyLocker::lock(ptr); + if (!TtyLocker::isLocked(ptr, &byCurrPid)) { + portError = SerialPort::PermissionDeniedError; + return false; + } + +#ifdef TIOCEXCL + ::ioctl(descriptor, TIOCEXCL); +#endif + + if (::tcgetattr(descriptor, &restoredTermios) == -1) { + portError = decodeSystemError(); + return false; + } + + ::memset(¤tTermios, 0, sizeof(currentTermios)); + ::cfmakeraw(¤tTermios); + currentTermios.c_cflag |= CLOCAL; + currentTermios.c_cc[VTIME] = 0; + currentTermios.c_cc[VMIN] = 0; + + if (mode & QIODevice::ReadOnly) + currentTermios.c_cflag |= CREAD; + + if (!updateTermios()) + return false; + + setExceptionNotificationEnabled(true); + + if ((flags & O_WRONLY) == 0) + setReadNotificationEnabled(true); + + detectDefaultSettings(); + return true; +} + +void SerialPortPrivate::close() +{ + if (restoreSettingsOnClose) { + ::tcsetattr(descriptor, TCSANOW, &restoredTermios); +#ifdef Q_OS_LINUX + if (isCustomRateSupported) + ::ioctl(descriptor, TIOCSSERIAL, &restoredSerialInfo); +#endif + } + +#ifdef TIOCNXCL + ::ioctl(descriptor, TIOCNXCL); +#endif + + if (readNotifier) { + readNotifier->setEnabled(false); + readNotifier->deleteLater(); + readNotifier = 0; + } + + if (writeNotifier) { + writeNotifier->setEnabled(false); + writeNotifier->deleteLater(); + writeNotifier = 0; + } + + if (exceptionNotifier) { + exceptionNotifier->setEnabled(false); + exceptionNotifier->deleteLater(); + exceptionNotifier = 0; + } + + ::close(descriptor); + + QByteArray portName = portNameFromSystemLocation(systemLocation).toLocal8Bit(); + const char *ptr = portName.constData(); + + bool byCurrPid = false; + if (TtyLocker::isLocked(ptr, &byCurrPid) && byCurrPid) + TtyLocker::unlock(ptr); + + descriptor = -1; + isCustomRateSupported = false; +} + +SerialPort::Lines SerialPortPrivate::lines() const +{ + int arg = 0; + SerialPort::Lines ret = 0; + + if (::ioctl(descriptor, TIOCMGET, &arg) == -1) + return ret; + +#ifdef TIOCM_LE + if (arg & TIOCM_LE) + ret |= SerialPort::Le; +#endif +#ifdef TIOCM_DTR + if (arg & TIOCM_DTR) + ret |= SerialPort::Dtr; +#endif +#ifdef TIOCM_RTS + if (arg & TIOCM_RTS) + ret |= SerialPort::Rts; +#endif +#ifdef TIOCM_ST + if (arg & TIOCM_ST) + ret |= SerialPort::St; +#endif +#ifdef TIOCM_SR + if (arg & TIOCM_SR) + ret |= SerialPort::Sr; +#endif +#ifdef TIOCM_CTS + if (arg & TIOCM_CTS) + ret |= SerialPort::Cts; +#endif +#ifdef TIOCM_CAR + if (arg & TIOCM_CAR) + ret |= SerialPort::Dcd; +#elif defined TIOCM_CD + if (arg & TIOCM_CD) + ret |= SerialPort::Dcd; +#endif +#ifdef TIOCM_RNG + if (arg & TIOCM_RNG) + ret |= SerialPort::Ri; +#elif defined TIOCM_RI + if (arg & TIOCM_RI) + ret |= SerialPort::Ri; +#endif +#ifdef TIOCM_DSR + if (arg & TIOCM_DSR) + ret |= SerialPort::Dsr; +#endif + + return ret; +} + +bool SerialPortPrivate::setDtr(bool set) +{ + int status = TIOCM_DTR; + return ::ioctl(descriptor, set ? TIOCMBIS : TIOCMBIC, &status) != -1; +} + +bool SerialPortPrivate::setRts(bool set) +{ + int status = TIOCM_RTS; + return ::ioctl(descriptor, set ? TIOCMBIS : TIOCMBIC, &status) != -1; +} + +bool SerialPortPrivate::flush() +{ + return writeNotification() && (::tcdrain(descriptor) != -1); +} + +bool SerialPortPrivate::clear(SerialPort::Directions dir) +{ + return ::tcflush(descriptor, (dir == SerialPort::AllDirections) + ? TCIOFLUSH : (dir & SerialPort::Input) ? TCIFLUSH : TCOFLUSH) != -1; +} + +bool SerialPortPrivate::sendBreak(int duration) +{ + return ::tcsendbreak(descriptor, duration) != -1; +} + +bool SerialPortPrivate::setBreak(bool set) +{ + return ::ioctl(descriptor, set ? TIOCSBRK : TIOCCBRK) != -1; +} + +qint64 SerialPortPrivate::bytesAvailable() const +{ + int nbytes = 0; +#ifdef TIOCINQ + if (::ioctl(descriptor, TIOCINQ, &nbytes) == -1) + return -1; +#endif + return nbytes; +} + +qint64 SerialPortPrivate::bytesToWrite() const +{ + int nbytes = 0; +#ifdef TIOCOUTQ + if (::ioctl(descriptor, TIOCOUTQ, &nbytes) == -1) + return -1; +#endif + return nbytes; +} + +qint64 SerialPortPrivate::readFromBuffer(char *data, qint64 maxSize) +{ + if (readBuffer.isEmpty()) + return 0; + + if (maxSize == 1) { + *data = readBuffer.getChar(); + if (readBuffer.isEmpty()) + setReadNotificationEnabled(true); + 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); + } + + if (!isReadNotificationEnabled()) + setReadNotificationEnabled(true); + + if (readSoFar > 0) { + if (readBuffer.isEmpty()) + setReadNotificationEnabled(true); + return readSoFar; + } + + 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); + + const qint64 written = maxSize; + + if (!writeBuffer.isEmpty() && !isWriteNotificationEnabled()) + setWriteNotificationEnabled(true); + + return written; +} + +bool SerialPortPrivate::waitForReadyRead(int msecs) +{ + QElapsedTimer stopWatch; + + stopWatch.start(); + + do { + bool readyToRead = false; + bool readyToWrite = false; + bool timedOut = false; + if (!waitForReadOrWrite(&readyToRead, &readyToWrite, true, !writeBuffer.isEmpty(), + timeoutValue(msecs, stopWatch.elapsed()), &timedOut)) { + // TODO: set error ? + return false; + } + + if (readyToRead) { + if (readNotification()) + return true; + } + + if (readyToWrite) + writeNotification(WriteChunkSize); + + } while (msecs == -1 || timeoutValue(msecs, stopWatch.elapsed()) > 0); + return false; +} + +bool SerialPortPrivate::waitForBytesWritten(int msecs) +{ + 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(msecs, stopWatch.elapsed()), &timedOut)) { + // TODO: set error ? + return false; + } + + if (readyToRead && !readNotification()) + return false; + + if (readyToWrite && writeNotification(WriteChunkSize)) + return true; + } + return false; +} + +bool SerialPortPrivate::setRate(qint32 rate, SerialPort::Directions dir) +{ + bool ret = rate > 0; + + // prepare section + + if (ret) { + const qint32 unixRate = SerialPortPrivate::settingFromRate(rate); + if (unixRate > 0) { + // try prepate to set standard baud rate +#ifdef Q_OS_LINUX + // prepare to forcefully reset the custom mode + if (isCustomRateSupported) { + //currentSerialInfo.flags |= ASYNC_SPD_MASK; + currentSerialInfo.flags &= ~(ASYNC_SPD_CUST /* | ASYNC_LOW_LATENCY*/); + currentSerialInfo.custom_divisor = 0; + } +#endif + // prepare to set standard rate + ret = !(((dir & SerialPort::Input) && ::cfsetispeed(¤tTermios, unixRate) < 0) + || ((dir & SerialPort::Output) && ::cfsetospeed(¤tTermios, unixRate) < 0)); + } else { + // try prepate to set custom baud rate +#ifdef Q_OS_LINUX + // prepare to forcefully set the custom mode + if (isCustomRateSupported) { + currentSerialInfo.flags &= ~ASYNC_SPD_MASK; + currentSerialInfo.flags |= (ASYNC_SPD_CUST /* | ASYNC_LOW_LATENCY*/); + currentSerialInfo.custom_divisor = currentSerialInfo.baud_base / rate; + if (currentSerialInfo.custom_divisor == 0) + currentSerialInfo.custom_divisor = 1; + // for custom mode needed prepare to set B38400 rate + ret = (::cfsetspeed(¤tTermios, B38400) != -1); + } else { + ret = false; + } +#elif defined(Q_OS_MAC) + +# if defined (MAC_OS_X_VERSION_10_4) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_4) + // Starting with Tiger, the IOSSIOSPEED ioctl can be used to set arbitrary baud rates + // other than those specified by POSIX. The driver for the underlying serial hardware + // ultimately determines which baud rates can be used. This ioctl sets both the input + // and output speed. + ret = ::ioctl(descriptor, IOSSIOSPEED, &rate) != -1; +# else + // others MacOSX version, can't prepare to set custom rate + ret = false; +# endif + +#else + // others *nix OS, can't prepare to set custom rate + ret = false; +#endif + } + } + + // finally section + +#ifdef Q_OS_LINUX + if (ret && isCustomRateSupported) // finally, set or reset the custom mode + ret = ::ioctl(descriptor, TIOCSSERIAL, ¤tSerialInfo) != -1; +#endif + + if (ret) // finally, set rate + ret = updateTermios(); + else + portError = decodeSystemError(); + return ret; +} + +bool SerialPortPrivate::setDataBits(SerialPort::DataBits dataBits) +{ + currentTermios.c_cflag &= ~CSIZE; + switch (dataBits) { + case SerialPort::Data5: + currentTermios.c_cflag |= CS5; + break; + case SerialPort::Data6: + currentTermios.c_cflag |= CS6; + break; + case SerialPort::Data7: + currentTermios.c_cflag |= CS7; + break; + case SerialPort::Data8: + currentTermios.c_cflag |= CS8; + break; + default: + currentTermios.c_cflag |= CS8; + break; + } + return updateTermios(); +} + +bool SerialPortPrivate::setParity(SerialPort::Parity parity) +{ + currentTermios.c_iflag &= ~(PARMRK | INPCK); + currentTermios.c_iflag |= IGNPAR; + + switch (parity) { + +#ifdef CMSPAR + // Here Installation parity only for GNU/Linux where the macro CMSPAR. + case SerialPort::SpaceParity: + currentTermios.c_cflag &= ~PARODD; + currentTermios.c_cflag |= PARENB | CMSPAR; + break; + case SerialPort::MarkParity: + currentTermios.c_cflag |= PARENB | CMSPAR | PARODD; + break; +#endif + case SerialPort::NoParity: + currentTermios.c_cflag &= ~PARENB; + break; + case SerialPort::EvenParity: + currentTermios.c_cflag &= ~PARODD; + currentTermios.c_cflag |= PARENB; + break; + case SerialPort::OddParity: + currentTermios.c_cflag |= PARENB | PARODD; + break; + default: + currentTermios.c_cflag |= PARENB; + currentTermios.c_iflag |= PARMRK | INPCK; + currentTermios.c_iflag &= ~IGNPAR; + break; + } + + return updateTermios(); +} + +bool SerialPortPrivate::setStopBits(SerialPort::StopBits stopBits) +{ + switch (stopBits) { + case SerialPort::OneStop: + currentTermios.c_cflag &= ~CSTOPB; + break; + case SerialPort::TwoStop: + currentTermios.c_cflag |= CSTOPB; + break; + default: + currentTermios.c_cflag &= ~CSTOPB; + break; + } + return updateTermios(); +} + +bool SerialPortPrivate::setFlowControl(SerialPort::FlowControl flow) +{ + switch (flow) { + case SerialPort::NoFlowControl: + currentTermios.c_cflag &= ~CRTSCTS; + currentTermios.c_iflag &= ~(IXON | IXOFF | IXANY); + break; + case SerialPort::HardwareControl: + currentTermios.c_cflag |= CRTSCTS; + currentTermios.c_iflag &= ~(IXON | IXOFF | IXANY); + break; + case SerialPort::SoftwareControl: + currentTermios.c_cflag &= ~CRTSCTS; + currentTermios.c_iflag |= IXON | IXOFF | IXANY; + break; + default: + currentTermios.c_cflag &= ~CRTSCTS; + currentTermios.c_iflag &= ~(IXON | IXOFF | IXANY); + break; + } + return updateTermios(); +} + +bool SerialPortPrivate::setDataErrorPolicy(SerialPort::DataErrorPolicy policy) +{ + tcflag_t parmrkMask = PARMRK; +#ifndef CMSPAR + // in space/mark parity emulation also used PARMRK flag + if (parity == SerialPort::SpaceParity + || parity == SerialPort::MarkParity) { + parmrkMask = 0; + } +#endif //CMSPAR + switch (policy) { + case SerialPort::SkipPolicy: + currentTermios.c_iflag &= ~parmrkMask; + currentTermios.c_iflag |= IGNPAR | INPCK; + break; + case SerialPort::PassZeroPolicy: + currentTermios.c_iflag &= ~(IGNPAR | parmrkMask); + currentTermios.c_iflag |= INPCK; + break; + case SerialPort::IgnorePolicy: + currentTermios.c_iflag &= ~INPCK; + break; + case SerialPort::StopReceivingPolicy: + currentTermios.c_iflag &= ~IGNPAR; + currentTermios.c_iflag |= parmrkMask | INPCK; + break; + default: + currentTermios.c_iflag &= ~INPCK; + break; + } + return updateTermios(); +} + +bool SerialPortPrivate::readNotification() +{ + // Prevent recursive calls + if (readPortNotifierCalled) { + if (!readPortNotifierStateSet) { + readPortNotifierStateSet = true; + readPortNotifierState = isReadNotificationEnabled(); + setReadNotificationEnabled(false); + } + } + + readPortNotifierCalled = true; + + // Always buffered, read data from the port into the read buffer + qint64 newBytes = readBuffer.size(); + qint64 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); + const qint64 readBytes = readFromPort(ptr, bytesToRead); + if (readBytes == -2) { + // No bytes currently available for reading. + readBuffer.chop(bytesToRead); + return true; + } + readBuffer.chop(bytesToRead - qMax(readBytes, qint64(0))); + + newBytes = readBuffer.size() - newBytes; + + // If read buffer is full, disable the read port notifier. + if (readBufferMaxSize && readBuffer.size() == readBufferMaxSize) + setReadNotificationEnabled(false); + + // only emit readyRead() when not recursing, and only if there is data available + const bool hasData = newBytes > 0; + + if (!emittedReadyRead && hasData) { + emittedReadyRead = true; + emit q_ptr->readyRead(); + emittedReadyRead = false; + } + + if (!hasData) + setReadNotificationEnabled(true); + + // reset the read port notifier state if we reentered inside the + // readyRead() connected slot. + if (readPortNotifierStateSet + && readPortNotifierState != isReadNotificationEnabled()) { + setReadNotificationEnabled(readPortNotifierState); + readPortNotifierStateSet = false; + } + return true; +} + +bool SerialPortPrivate::writeNotification(int maxSize) +{ + const int tmp = writeBuffer.size(); + + if (writeBuffer.isEmpty()) { + setWriteNotificationEnabled(false); + return false; + } + + int nextSize = qMin(writeBuffer.nextDataBlockSize(), maxSize); + + const char *ptr = writeBuffer.readPointer(); + + // Attempt to write it chunk. + qint64 written = writeToPort(ptr, nextSize); + if (written < 0) { + // TODO: set error? + return false; + } + + // Remove what we wrote so far. + writeBuffer.free(written); + if (written > 0) { + // Don't emit bytesWritten() recursively. + if (!emittedBytesWritten) { + emittedBytesWritten = true; + emit q_ptr->bytesWritten(written); + emittedBytesWritten = false; + } + } + + if (writeBuffer.isEmpty()) + setWriteNotificationEnabled(false); + + return (writeBuffer.size() < tmp); +} + +bool SerialPortPrivate::exceptionNotification() +{ + // FIXME: + return false; +} + +bool SerialPortPrivate::updateTermios() +{ + if (::tcsetattr(descriptor, TCSANOW, ¤tTermios) == -1) { + portError = decodeSystemError(); + return false; + } + return true; +} + +void SerialPortPrivate::detectDefaultSettings() +{ + // Detect rate. + const speed_t inputUnixRate = ::cfgetispeed(¤tTermios); + const speed_t outputUnixRate = ::cfgetospeed(¤tTermios); + bool isCustomRateCurrentSet = false; + +#ifdef Q_OS_LINUX + // try detect the ability to support custom rate + isCustomRateSupported = ::ioctl(descriptor, TIOCGSERIAL, ¤tSerialInfo) != -1 + && ::ioctl(descriptor, TIOCSSERIAL, ¤tSerialInfo) != -1; + + if (isCustomRateSupported) { + restoredSerialInfo = currentSerialInfo; + + // assume that the baud rate is a custom + isCustomRateCurrentSet = inputUnixRate == B38400 && outputUnixRate == B38400; + + if (isCustomRateCurrentSet) { + if ((currentSerialInfo.flags & ASYNC_SPD_CUST) + && currentSerialInfo.custom_divisor > 0) { + + // yes, speed is really custom + inputRate = currentSerialInfo.baud_base / currentSerialInfo.custom_divisor; + outputRate = inputRate; + } else { + // no, we were wrong and the speed is a standard 38400 baud + isCustomRateCurrentSet = false; + } + } + } +#else + // other *nix +#endif + if (!isCustomRateSupported || !isCustomRateCurrentSet) { + inputRate = SerialPortPrivate::rateFromSetting(inputUnixRate); + outputRate = SerialPortPrivate::rateFromSetting(outputUnixRate); + } + + // Detect databits. + switch (currentTermios.c_cflag & CSIZE) { + case CS5: + dataBits = SerialPort::Data5; + break; + case CS6: + dataBits = SerialPort::Data6; + break; + case CS7: + dataBits = SerialPort::Data7; + break; + case CS8: + dataBits = SerialPort::Data8; + break; + default: + dataBits = SerialPort::UnknownDataBits; + break; + } + + // Detect parity. +#ifdef CMSPAR + if (currentTermios.c_cflag & CMSPAR) { + parity = currentTermios.c_cflag & PARODD ? + SerialPort::MarkParity : SerialPort::SpaceParity; + } else { +#endif + if (currentTermios.c_cflag & PARENB) { + parity = currentTermios.c_cflag & PARODD ? + SerialPort::OddParity : SerialPort::EvenParity; + } else { + parity = SerialPort::NoParity; + } +#ifdef CMSPAR + } +#endif + + // Detect stopbits. + stopBits = currentTermios.c_cflag & CSTOPB ? + SerialPort::TwoStop : SerialPort::OneStop; + + // Detect flow control. + if ((!(currentTermios.c_cflag & CRTSCTS)) && (!(currentTermios.c_iflag & (IXON | IXOFF | IXANY)))) + flow = SerialPort::NoFlowControl; + else if ((!(currentTermios.c_cflag & CRTSCTS)) && (currentTermios.c_iflag & (IXON | IXOFF | IXANY))) + flow = SerialPort::SoftwareControl; + else if ((currentTermios.c_cflag & CRTSCTS) && (!(currentTermios.c_iflag & (IXON | IXOFF | IXANY)))) + flow = SerialPort::HardwareControl; + else + flow = SerialPort::UnknownFlowControl; +} + +SerialPort::PortError SerialPortPrivate::decodeSystemError() const +{ + SerialPort::PortError error; + switch (errno) { + case ENODEV: + error = SerialPort::NoSuchDeviceError; + break; + case EACCES: + error = SerialPort::PermissionDeniedError; + break; + case EBUSY: + error = SerialPort::PermissionDeniedError; + break; + case ENOTTY: + error = SerialPort::IoError; + break; + default: + error = SerialPort::UnknownPortError; + break; + } + return error; +} + +bool SerialPortPrivate::isReadNotificationEnabled() const +{ + return readNotifier && readNotifier->isEnabled(); +} + +void SerialPortPrivate::setReadNotificationEnabled(bool enable) +{ + if (readNotifier) { + readNotifier->setEnabled(enable); + } else if (enable) { + readNotifier = new ReadNotifier(this, q_ptr); + readNotifier->setEnabled(true); + } +} + +bool SerialPortPrivate::isWriteNotificationEnabled() const +{ + return writeNotifier && writeNotifier->isEnabled(); +} + +void SerialPortPrivate::setWriteNotificationEnabled(bool enable) +{ + if (writeNotifier) { + writeNotifier->setEnabled(enable); + } else if (enable) { + writeNotifier = new WriteNotifier(this, q_ptr); + writeNotifier->setEnabled(true); + } +} + +bool SerialPortPrivate::isExceptionNotificationEnabled() const +{ + return exceptionNotifier && exceptionNotifier->isEnabled(); +} + +void SerialPortPrivate::setExceptionNotificationEnabled(bool enable) +{ + if (exceptionNotifier) { + exceptionNotifier->setEnabled(enable); + } else if (enable) { + exceptionNotifier = new ExceptionNotifier(this, q_ptr); + exceptionNotifier->setEnabled(true); + } +} + +bool SerialPortPrivate::waitForReadOrWrite(bool *selectForRead, bool *selectForWrite, + bool checkRead, bool checkWrite, + int msecs, bool *timedOut) +{ + Q_ASSERT(selectForRead); + Q_ASSERT(selectForWrite); + Q_ASSERT(timedOut); + + fd_set fdread; + FD_ZERO(&fdread); + if (checkRead) + FD_SET(descriptor, &fdread); + + fd_set fdwrite; + FD_ZERO(&fdwrite); + if (checkWrite) + FD_SET(descriptor, &fdwrite); + + struct timeval tv; + tv.tv_sec = msecs / 1000; + tv.tv_usec = (msecs % 1000) * 1000; + + int ret = ::select(descriptor + 1, &fdread, &fdwrite, 0, msecs < 0 ? 0 : &tv); + if (ret < 0) + return false; + if (ret == 0) { + *timedOut = true; + return false; + } + + *selectForRead = FD_ISSET(descriptor, &fdread); + *selectForWrite = FD_ISSET(descriptor, &fdwrite); + + return ret; +} + +qint64 SerialPortPrivate::readFromPort(char *data, qint64 maxSize) +{ + qint64 bytesRead = 0; +#if defined (CMSPAR) + if (parity == SerialPort::NoParity + || policy != SerialPort::StopReceivingPolicy) { +#else + if (parity != SerialPort::MarkParity + && parity != SerialPort::SpaceParity) { +#endif + bytesRead = ::read(descriptor, data, maxSize); + } else {// Perform parity emulation. + bytesRead = readPerChar(data, maxSize); + } + + // FIXME: Here 'errno' codes for sockets. + // You need to replace the codes for the serial port. + if (bytesRead < 0) { + bytesRead = -1; + switch (errno) { +#if EWOULDBLOCK-0 && EWOULDBLOCK != EAGAIN + case EWOULDBLOCK: +#endif + case EAGAIN: + // No data was available for reading. + bytesRead = -2; + break; + case EBADF: + case EINVAL: + case EIO: + break; + case ECONNRESET: + bytesRead = 0; + break; + default: + break; + } + } + return bytesRead; +} + +qint64 SerialPortPrivate::writeToPort(const char *data, qint64 maxSize) +{ + qint64 bytesWritten = 0; +#if defined (CMSPAR) + bytesWritten = ::write(descriptor, data, maxSize); +#else + if (parity != SerialPort::MarkParity + && parity != SerialPort::SpaceParity) { + bytesWritten = ::write(descriptor, data, maxSize); + } else {// Perform parity emulation. + bytesWritten = writePerChar(data, maxSize); + } +#endif + + // FIXME: Here 'errno' codes for sockets. + // You need to replace the codes for the serial port. + if (bytesWritten < 0) { + switch (errno) { + case EPIPE: + case ECONNRESET: + bytesWritten = -1; + break; + case EAGAIN: + bytesWritten = 0; + break; + case EMSGSIZE: + break; + default: + break; + } + } + return bytesWritten; +} + +static inline bool evenParity(quint8 c) +{ + c ^= c >> 4; //(c7 ^ c3)(c6 ^ c2)(c5 ^ c1)(c4 ^ c0) + c ^= c >> 2; //[(c7 ^ c3)(c5 ^ c1)][(c6 ^ c2)(c4 ^ c0)] + c ^= c >> 1; + return c & 1; //(c7 ^ c3)(c5 ^ c1)(c6 ^ c2)(c4 ^ c0) +} + +#ifndef CMSPAR + +qint64 SerialPortPrivate::writePerChar(const char *data, qint64 maxSize) +{ + qint64 ret = 0; + quint8 const charMask = (0xFF >> (8 - dataBits)); + + while (ret < maxSize) { + + bool par = evenParity(*data & charMask); + // False if need EVEN, true if need ODD. + par ^= parity == SerialPort::MarkParity; + if (par ^ (currentTermios.c_cflag & PARODD)) { // Need switch parity mode? + currentTermios.c_cflag ^= PARODD; + flush(); //force sending already buffered data, because updateTermios() cleares buffers + //todo: add receiving buffered data!!! + if (!updateTermios()) + break; + } + + int r = ::write(descriptor, data, 1); + if (r < 0) + return -1; + if (r > 0) { + data += r; + ret += r; + } + } + return ret; +} + +#endif //CMSPAR + +qint64 SerialPortPrivate::readPerChar(char *data, qint64 maxSize) +{ + qint64 ret = 0; + quint8 const charMask = (0xFF >> (8 - dataBits)); + + // 0 - prefix not started, + // 1 - received 0xFF, + // 2 - received 0xFF and 0x00 + int prefix = 0; + while (ret < maxSize) { + + qint64 r = ::read(descriptor, data, 1); + if (r < 0) { + if (errno == EAGAIN) // It is ok for nonblocking mode. + break; + return -1; + } + if (r == 0) + break; + + bool par = true; + switch (prefix) { + case 2: // Previously received both 0377 and 0. + par = false; + prefix = 0; + break; + case 1: // Previously received 0377. + if (*data == '\0') { + ++prefix; + continue; + } + prefix = 0; + break; + default: + if (*data == '\377') { + prefix = 1; + continue; + } + break; + } + // Now: par contains parity ok or error, *data contains received character + par ^= evenParity(*data & charMask); //par contains parity bit value for EVEN mode + par ^= (currentTermios.c_cflag & PARODD); //par contains parity bit value for current mode + if (par ^ (parity == SerialPort::SpaceParity)) { //if parity error + switch (policy) { + case SerialPort::SkipPolicy: + continue; //ignore received character + case SerialPort::StopReceivingPolicy: + if (parity != SerialPort::NoParity) + portError = SerialPort::ParityError; + else + portError = *data == '\0' ? + SerialPort::BreakConditionError : SerialPort::FramingError; + return ++ret; //abort receiving + break; + case SerialPort::UnknownPolicy: + // Unknown error policy is used! Falling back to PassZeroPolicy + case SerialPort::PassZeroPolicy: + *data = '\0'; //replace received character by zero + break; + case SerialPort::IgnorePolicy: + break; //ignore error and pass received character + } + } + ++data; + ++ret; + } + return ret; +} + +#ifdef Q_OS_MAC +static const QLatin1String defaultPathPrefix("/dev/cu."); +static const QLatin1String notUsedPathPrefix("/dev/tty."); +#else +static const QLatin1String defaultPathPrefix("/dev/"); +#endif + +QString SerialPortPrivate::portNameToSystemLocation(const QString &port) +{ + QString ret = port; + +#ifdef Q_OS_MAC + ret.remove(notUsedPathPrefix); +#endif + + if (!ret.contains(defaultPathPrefix)) + ret.prepend(defaultPathPrefix); + return ret; +} + +QString SerialPortPrivate::portNameFromSystemLocation(const QString &location) +{ + QString ret = location; + +#ifdef Q_OS_MAC + ret.remove(notUsedPathPrefix); +#endif + + ret.remove(defaultPathPrefix); + return ret; +} + +struct RatePair +{ + qint32 rate; // The numerical value of baud rate. + qint32 setting; // The OS-specific code of baud rate. + bool operator<(const RatePair &other) const { return rate < other.rate; } + bool operator==(const RatePair &other) const { return setting == other.setting; } +}; + +// This table contains correspondences standard pairs values of +// baud rates that are defined in file termios.h +static const RatePair standardRatesTable[] = +{ + #ifdef B50 + { 50, B50 }, + #endif + #ifdef B75 + { 75, B75 }, + #endif + #ifdef B110 + { 110, B110 }, + #endif + #ifdef B134 + { 134, B134 }, + #endif + #ifdef B150 + { 150, B150 }, + #endif + #ifdef B200 + { 200, B200 }, + #endif + #ifdef B300 + { 300, B300 }, + #endif + #ifdef B600 + { 600, B600 }, + #endif + #ifdef B1200 + { 1200, B1200 }, + #endif + #ifdef B1800 + { 1800, B1800 }, + #endif + #ifdef B2400 + { 2400, B2400 }, + #endif + #ifdef B4800 + { 4800, B4800 }, + #endif + #ifdef B9600 + { 9600, B9600 }, + #endif + #ifdef B19200 + { 19200, B19200 }, + #endif + #ifdef B38400 + { 38400, B38400 }, + #endif + #ifdef B57600 + { 57600, B57600 }, + #endif + #ifdef B115200 + { 115200, B115200 }, + #endif + #ifdef B230400 + { 230400, B230400 }, + #endif + #ifdef B460800 + { 460800, B460800 }, + #endif + #ifdef B500000 + { 500000, B500000 }, + #endif + #ifdef B576000 + { 576000, B576000 }, + #endif + #ifdef B921600 + { 921600, B921600 }, + #endif + #ifdef B1000000 + { 1000000, B1000000 }, + #endif + #ifdef B1152000 + { 1152000, B1152000 }, + #endif + #ifdef B1500000 + { 1500000, B1500000 }, + #endif + #ifdef B2000000 + { 2000000, B2000000}, + #endif + #ifdef B2500000 + { 2500000, B2500000 }, + #endif + #ifdef B3000000 + { 3000000, B3000000 }, + #endif + #ifdef B3500000 + { 3500000, B3500000 }, + #endif + #ifdef B4000000 + { 4000000, B4000000 } + #endif +}; + +static const RatePair *standardRatesTable_end = + standardRatesTable + sizeof(standardRatesTable)/sizeof(*standardRatesTable); + +qint32 SerialPortPrivate::rateFromSetting(qint32 setting) +{ + const RatePair rp = { 0, setting }; + const RatePair *ret = qFind(standardRatesTable, standardRatesTable_end, rp); + return ret != standardRatesTable_end ? ret->rate : 0; +} + +qint32 SerialPortPrivate::settingFromRate(qint32 rate) +{ + const RatePair rp = { rate, 0 }; + const RatePair *ret = qBinaryFind(standardRatesTable, standardRatesTable_end, rp); + return ret != standardRatesTable_end ? ret->setting : 0; +} + +QList<qint32> SerialPortPrivate::standardRates() +{ + QList<qint32> ret; + for (const RatePair *it = standardRatesTable; it != standardRatesTable_end; ++it) + ret.append(it->rate); + return ret; +} + +QT_END_NAMESPACE_SERIALPORT |