/**************************************************************************** ** ** Copyright (C) 2012 Denis Shienkov ** Copyright (C) 2012 Laszlo Papp ** Copyright (C) 2012 Andre Hartmann ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtSerialPort module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qserialport_p.h" #include "qtntdll_p.h" #include #include #include #include #include #include QT_BEGIN_NAMESPACE static inline void qt_set_common_props(DCB *dcb) { dcb->fBinary = TRUE; dcb->fAbortOnError = FALSE; dcb->fNull = FALSE; dcb->fErrorChar = FALSE; if (dcb->fDtrControl == DTR_CONTROL_HANDSHAKE) dcb->fDtrControl = DTR_CONTROL_DISABLE; if (dcb->fRtsControl != RTS_CONTROL_HANDSHAKE) dcb->fRtsControl = RTS_CONTROL_DISABLE; } static inline void qt_set_baudrate(DCB *dcb, qint32 baudrate) { dcb->BaudRate = baudrate; } static inline void qt_set_databits(DCB *dcb, QSerialPort::DataBits databits) { dcb->ByteSize = databits; } static inline void qt_set_parity(DCB *dcb, QSerialPort::Parity parity) { dcb->fParity = TRUE; switch (parity) { case QSerialPort::NoParity: dcb->Parity = NOPARITY; dcb->fParity = FALSE; break; case QSerialPort::OddParity: dcb->Parity = ODDPARITY; break; case QSerialPort::EvenParity: dcb->Parity = EVENPARITY; break; case QSerialPort::MarkParity: dcb->Parity = MARKPARITY; break; case QSerialPort::SpaceParity: dcb->Parity = SPACEPARITY; break; default: dcb->Parity = NOPARITY; dcb->fParity = FALSE; break; } } static inline void qt_set_stopbits(DCB *dcb, QSerialPort::StopBits stopbits) { switch (stopbits) { case QSerialPort::OneStop: dcb->StopBits = ONESTOPBIT; break; case QSerialPort::OneAndHalfStop: dcb->StopBits = ONE5STOPBITS; break; case QSerialPort::TwoStop: dcb->StopBits = TWOSTOPBITS; break; default: dcb->StopBits = ONESTOPBIT; break; } } static inline void qt_set_flowcontrol(DCB *dcb, QSerialPort::FlowControl flowcontrol) { dcb->fInX = FALSE; dcb->fOutX = FALSE; dcb->fOutxCtsFlow = FALSE; if (dcb->fRtsControl == RTS_CONTROL_HANDSHAKE) dcb->fRtsControl = RTS_CONTROL_DISABLE; switch (flowcontrol) { case QSerialPort::NoFlowControl: break; case QSerialPort::SoftwareControl: dcb->fInX = TRUE; dcb->fOutX = TRUE; break; case QSerialPort::HardwareControl: dcb->fOutxCtsFlow = TRUE; dcb->fRtsControl = RTS_CONTROL_HANDSHAKE; break; default: break; } } // Translate NT-callbacks to Win32 callbacks. static VOID WINAPI qt_apc_routine( PVOID context, PIO_STATUS_BLOCK ioStatusBlock, DWORD reserved) { Q_UNUSED(reserved); const DWORD errorCode = ::RtlNtStatusToDosError(ioStatusBlock->Status); const DWORD bytesTransfered = NT_SUCCESS(ioStatusBlock->Status) ? DWORD(ioStatusBlock->Information) : 0; const LPOVERLAPPED overlapped = CONTAINING_RECORD(ioStatusBlock, OVERLAPPED, Internal); (reinterpret_cast(context)) (errorCode, bytesTransfered, overlapped); } // Alertable analog of DeviceIoControl function. static BOOL qt_device_io_control_ex( HANDLE deviceHandle, DWORD ioControlCode, LPVOID inputBuffer, DWORD inputBufferSize, LPVOID outputBuffer, DWORD outputBufferSize, LPOVERLAPPED overlapped, LPOVERLAPPED_COMPLETION_ROUTINE completionRoutine) { const auto ioStatusBlock = reinterpret_cast( &overlapped->Internal); ioStatusBlock->Status = STATUS_PENDING; const NTSTATUS status = ::NtDeviceIoControlFile( deviceHandle, nullptr, qt_apc_routine, reinterpret_cast(completionRoutine), ioStatusBlock, ioControlCode, inputBuffer, inputBufferSize, outputBuffer, outputBufferSize); if (!NT_SUCCESS(status)) { ::SetLastError(::RtlNtStatusToDosError(status)); return false; } return true; } // Alertable analog of WaitCommEvent function. static BOOL qt_wait_comm_event_ex( HANDLE deviceHandle, LPDWORD eventsMask, LPOVERLAPPED overlapped, LPOVERLAPPED_COMPLETION_ROUTINE completionRoutine) { return qt_device_io_control_ex( deviceHandle, IOCTL_SERIAL_WAIT_ON_MASK, nullptr, 0, eventsMask, sizeof(DWORD), overlapped, completionRoutine); } struct RuntimeHelper { QLibrary ntLibrary; QBasicMutex mutex; }; Q_GLOBAL_STATIC(RuntimeHelper, helper) class Overlapped final : public OVERLAPPED { Q_DISABLE_COPY(Overlapped) public: explicit Overlapped(QSerialPortPrivate *d); void clear(); QSerialPortPrivate *dptr = nullptr; }; Overlapped::Overlapped(QSerialPortPrivate *d) : dptr(d) { } void Overlapped::clear() { ::ZeroMemory(this, sizeof(OVERLAPPED)); } bool QSerialPortPrivate::open(QIODevice::OpenMode mode) { { QMutexLocker locker(&helper()->mutex); static bool symbolsResolved = resolveSymbols(&helper()->ntLibrary); if (!symbolsResolved) { setError(QSerialPortErrorInfo(QSerialPort::OpenError, helper()->ntLibrary.errorString())); return false; } } DWORD desiredAccess = 0; if (mode & QIODevice::ReadOnly) desiredAccess |= GENERIC_READ; if (mode & QIODevice::WriteOnly) desiredAccess |= GENERIC_WRITE; handle = ::CreateFile(reinterpret_cast(systemLocation.utf16()), desiredAccess, 0, nullptr, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, nullptr); if (handle == INVALID_HANDLE_VALUE) { setError(getSystemError()); return false; } if (initialize(mode)) return true; ::CloseHandle(handle); return false; } void QSerialPortPrivate::close() { delete startAsyncWriteTimer; startAsyncWriteTimer = nullptr; if (communicationStarted) { communicationCompletionOverlapped->dptr = nullptr; ::CancelIoEx(handle, communicationCompletionOverlapped); // The object will be deleted in the I/O callback. communicationCompletionOverlapped = nullptr; communicationStarted = false; } else { delete communicationCompletionOverlapped; communicationCompletionOverlapped = nullptr; } if (readStarted) { readCompletionOverlapped->dptr = nullptr; ::CancelIoEx(handle, readCompletionOverlapped); // The object will be deleted in the I/O callback. readCompletionOverlapped = nullptr; readStarted = false; } else { delete readCompletionOverlapped; readCompletionOverlapped = nullptr; }; if (writeStarted) { writeCompletionOverlapped->dptr = nullptr; ::CancelIoEx(handle, writeCompletionOverlapped); // The object will be deleted in the I/O callback. writeCompletionOverlapped = nullptr; writeStarted = false; } else { delete writeCompletionOverlapped; writeCompletionOverlapped = nullptr; } readBytesTransferred = 0; writeBytesTransferred = 0; writeBuffer.clear(); if (settingsRestoredOnClose) { ::SetCommState(handle, &restoredDcb); ::SetCommTimeouts(handle, &restoredCommTimeouts); } ::CloseHandle(handle); handle = INVALID_HANDLE_VALUE; } QSerialPort::PinoutSignals QSerialPortPrivate::pinoutSignals() { DWORD modemStat = 0; if (!::GetCommModemStatus(handle, &modemStat)) { setError(getSystemError()); return QSerialPort::NoSignal; } QSerialPort::PinoutSignals ret = QSerialPort::NoSignal; if (modemStat & MS_CTS_ON) ret |= QSerialPort::ClearToSendSignal; if (modemStat & MS_DSR_ON) ret |= QSerialPort::DataSetReadySignal; if (modemStat & MS_RING_ON) ret |= QSerialPort::RingIndicatorSignal; if (modemStat & MS_RLSD_ON) ret |= QSerialPort::DataCarrierDetectSignal; DWORD bytesReturned = 0; if (!::DeviceIoControl(handle, IOCTL_SERIAL_GET_DTRRTS, nullptr, 0, &modemStat, sizeof(modemStat), &bytesReturned, nullptr)) { setError(getSystemError()); return ret; } if (modemStat & SERIAL_DTR_STATE) ret |= QSerialPort::DataTerminalReadySignal; if (modemStat & SERIAL_RTS_STATE) ret |= QSerialPort::RequestToSendSignal; return ret; } bool QSerialPortPrivate::setDataTerminalReady(bool set) { if (!::EscapeCommFunction(handle, set ? SETDTR : CLRDTR)) { setError(getSystemError()); return false; } DCB dcb; if (!getDcb(&dcb)) return false; dcb.fDtrControl = set ? DTR_CONTROL_ENABLE : DTR_CONTROL_DISABLE; return setDcb(&dcb); } bool QSerialPortPrivate::setRequestToSend(bool set) { if (!::EscapeCommFunction(handle, set ? SETRTS : CLRRTS)) { setError(getSystemError()); return false; } DCB dcb; if (!getDcb(&dcb)) return false; dcb.fRtsControl = set ? RTS_CONTROL_ENABLE : RTS_CONTROL_DISABLE; return setDcb(&dcb); } bool QSerialPortPrivate::flush() { return _q_startAsyncWrite(); } bool QSerialPortPrivate::clear(QSerialPort::Directions directions) { DWORD flags = 0; if (directions & QSerialPort::Input) flags |= PURGE_RXABORT | PURGE_RXCLEAR; if (directions & QSerialPort::Output) flags |= PURGE_TXABORT | PURGE_TXCLEAR; if (!::PurgeComm(handle, flags)) { setError(getSystemError()); return false; } // We need start async read because a reading can be stalled. Since the // PurgeComm can abort of current reading sequence, or a port is in hardware // flow control mode, or a port has a limited read buffer size. if (directions & QSerialPort::Input) startAsyncCommunication(); return true; } bool QSerialPortPrivate::sendBreak(int duration) { if (!setBreakEnabled(true)) return false; ::Sleep(duration); if (!setBreakEnabled(false)) return false; return true; } bool QSerialPortPrivate::setBreakEnabled(bool set) { if (set ? !::SetCommBreak(handle) : !::ClearCommBreak(handle)) { setError(getSystemError()); return false; } return true; } bool QSerialPortPrivate::waitForReadyRead(int msecs) { if (!writeStarted && !_q_startAsyncWrite()) return false; QDeadlineTimer deadline(msecs); do { if (readBytesTransferred <= 0) { const qint64 remaining = deadline.remainingTime(); const DWORD result = ::SleepEx( remaining == -1 ? INFINITE : DWORD(remaining), TRUE); if (result != WAIT_IO_COMPLETION) continue; } if (readBytesTransferred > 0) { readBytesTransferred = 0; return true; } } while (!deadline.hasExpired()); setError(getSystemError(WAIT_TIMEOUT)); return false; } bool QSerialPortPrivate::waitForBytesWritten(int msecs) { if (writeBuffer.isEmpty() && writeChunkBuffer.isEmpty()) return false; if (!writeStarted && !_q_startAsyncWrite()) return false; QDeadlineTimer deadline(msecs); do { if (writeBytesTransferred <= 0) { const qint64 remaining = deadline.remainingTime(); const DWORD result = ::SleepEx( remaining == -1 ? INFINITE : DWORD(remaining), TRUE); if (result != WAIT_IO_COMPLETION) continue; } if (writeBytesTransferred > 0) { writeBytesTransferred = 0; return true; } } while (!deadline.hasExpired()); setError(getSystemError(WAIT_TIMEOUT)); return false; } bool QSerialPortPrivate::setBaudRate() { return setBaudRate(inputBaudRate, QSerialPort::AllDirections); } bool QSerialPortPrivate::setBaudRate(qint32 baudRate, QSerialPort::Directions directions) { if (directions != QSerialPort::AllDirections) { setError(QSerialPortErrorInfo(QSerialPort::UnsupportedOperationError, QSerialPort::tr("Custom baud rate direction is unsupported"))); return false; } DCB dcb; if (!getDcb(&dcb)) return false; qt_set_baudrate(&dcb, baudRate); return setDcb(&dcb); } bool QSerialPortPrivate::setDataBits(QSerialPort::DataBits dataBits) { DCB dcb; if (!getDcb(&dcb)) return false; qt_set_databits(&dcb, dataBits); return setDcb(&dcb); } bool QSerialPortPrivate::setParity(QSerialPort::Parity parity) { DCB dcb; if (!getDcb(&dcb)) return false; qt_set_parity(&dcb, parity); return setDcb(&dcb); } bool QSerialPortPrivate::setStopBits(QSerialPort::StopBits stopBits) { DCB dcb; if (!getDcb(&dcb)) return false; qt_set_stopbits(&dcb, stopBits); return setDcb(&dcb); } bool QSerialPortPrivate::setFlowControl(QSerialPort::FlowControl flowControl) { DCB dcb; if (!getDcb(&dcb)) return false; qt_set_flowcontrol(&dcb, flowControl); return setDcb(&dcb); } bool QSerialPortPrivate::completeAsyncCommunication(qint64 bytesTransferred) { communicationStarted = false; if (bytesTransferred == qint64(-1)) return false; return startAsyncRead(); } bool QSerialPortPrivate::completeAsyncRead(qint64 bytesTransferred) { // Store the number of transferred bytes which are // required only in waitForReadyRead() method. readBytesTransferred = bytesTransferred; if (bytesTransferred == qint64(-1)) { readStarted = false; return false; } if (bytesTransferred > 0) buffer.append(readChunkBuffer.constData(), bytesTransferred); readStarted = false; bool result = true; if (bytesTransferred == QSERIALPORT_BUFFERSIZE || queuedBytesCount(QSerialPort::Input) > 0) { result = startAsyncRead(); } else { result = startAsyncCommunication(); } if (bytesTransferred > 0) emitReadyRead(); return result; } bool QSerialPortPrivate::completeAsyncWrite(qint64 bytesTransferred) { Q_Q(QSerialPort); // Store the number of transferred bytes which are // required only in waitForBytesWritten() method. writeBytesTransferred = bytesTransferred; if (writeStarted) { if (bytesTransferred == qint64(-1)) { writeChunkBuffer.clear(); writeStarted = false; return false; } Q_ASSERT(bytesTransferred == writeChunkBuffer.size()); writeChunkBuffer.clear(); emit q->bytesWritten(bytesTransferred); writeStarted = false; } return _q_startAsyncWrite(); } bool QSerialPortPrivate::startAsyncCommunication() { if (communicationStarted) return true; if (!communicationCompletionOverlapped) communicationCompletionOverlapped = new Overlapped(this); communicationCompletionOverlapped->clear(); communicationStarted = true; if (!::qt_wait_comm_event_ex(handle, &triggeredEventMask, communicationCompletionOverlapped, ioCompletionRoutine)) { communicationStarted = false; QSerialPortErrorInfo error = getSystemError(); if (error.errorCode != QSerialPort::NoError) { if (error.errorCode == QSerialPort::PermissionError) error.errorCode = QSerialPort::ResourceError; setError(error); return false; } } return true; } bool QSerialPortPrivate::startAsyncRead() { if (readStarted) return true; qint64 bytesToRead = QSERIALPORT_BUFFERSIZE; if (readBufferMaxSize && bytesToRead > (readBufferMaxSize - buffer.size())) { bytesToRead = readBufferMaxSize - buffer.size(); if (bytesToRead <= 0) { // Buffer is full. User must read data from the buffer // before we can read more from the port. return false; } } Q_ASSERT(int(bytesToRead) <= readChunkBuffer.size()); if (!readCompletionOverlapped) readCompletionOverlapped = new Overlapped(this); readCompletionOverlapped->clear(); readStarted = true; if (!::ReadFileEx(handle, readChunkBuffer.data(), bytesToRead, readCompletionOverlapped, ioCompletionRoutine)) { readStarted = false; QSerialPortErrorInfo error = getSystemError(); if (error.errorCode != QSerialPort::NoError) { if (error.errorCode == QSerialPort::PermissionError) error.errorCode = QSerialPort::ResourceError; if (error.errorCode != QSerialPort::ResourceError) error.errorCode = QSerialPort::ReadError; setError(error); return false; } } return true; } bool QSerialPortPrivate::_q_startAsyncWrite() { if (writeBuffer.isEmpty() || writeStarted) return true; writeChunkBuffer = writeBuffer.read(); if (!writeCompletionOverlapped) writeCompletionOverlapped = new Overlapped(this); writeCompletionOverlapped->clear(); writeStarted = true; if (!::WriteFileEx(handle, writeChunkBuffer.constData(), writeChunkBuffer.size(), writeCompletionOverlapped, ioCompletionRoutine)) { writeStarted = false; QSerialPortErrorInfo error = getSystemError(); if (error.errorCode != QSerialPort::NoError) { if (error.errorCode != QSerialPort::ResourceError) error.errorCode = QSerialPort::WriteError; setError(error); return false; } } return true; } void QSerialPortPrivate::handleNotification(DWORD bytesTransferred, DWORD errorCode, OVERLAPPED *overlapped) { // This occurred e.g. after calling the CloseHandle() function, // just skip handling at all. if (handle == INVALID_HANDLE_VALUE) return; const QSerialPortErrorInfo error = getSystemError(errorCode); if (error.errorCode != QSerialPort::NoError) { setError(error); return; } if (overlapped == communicationCompletionOverlapped) completeAsyncCommunication(bytesTransferred); else if (overlapped == readCompletionOverlapped) completeAsyncRead(bytesTransferred); else if (overlapped == writeCompletionOverlapped) completeAsyncWrite(bytesTransferred); else Q_ASSERT(!"Unknown OVERLAPPED activated"); } void QSerialPortPrivate::emitReadyRead() { Q_Q(QSerialPort); emit q->readyRead(); } qint64 QSerialPortPrivate::writeData(const char *data, qint64 maxSize) { Q_Q(QSerialPort); writeBuffer.append(data, maxSize); if (!writeBuffer.isEmpty() && !writeStarted) { if (!startAsyncWriteTimer) { startAsyncWriteTimer = new QTimer(q); QObjectPrivate::connect(startAsyncWriteTimer, &QTimer::timeout, this, &QSerialPortPrivate::_q_startAsyncWrite); startAsyncWriteTimer->setSingleShot(true); } if (!startAsyncWriteTimer->isActive()) startAsyncWriteTimer->start(); } return maxSize; } qint64 QSerialPortPrivate::queuedBytesCount(QSerialPort::Direction direction) const { COMSTAT comstat; if (::ClearCommError(handle, nullptr, &comstat) == 0) return -1; return (direction == QSerialPort::Input) ? comstat.cbInQue : ((direction == QSerialPort::Output) ? comstat.cbOutQue : -1); } inline bool QSerialPortPrivate::initialize(QIODevice::OpenMode mode) { DCB dcb; if (!getDcb(&dcb)) return false; restoredDcb = dcb; qt_set_common_props(&dcb); qt_set_baudrate(&dcb, inputBaudRate); qt_set_databits(&dcb, dataBits); qt_set_parity(&dcb, parity); qt_set_stopbits(&dcb, stopBits); qt_set_flowcontrol(&dcb, flowControl); if (!setDcb(&dcb)) return false; if (!::GetCommTimeouts(handle, &restoredCommTimeouts)) { setError(getSystemError()); return false; } ::ZeroMemory(¤tCommTimeouts, sizeof(currentCommTimeouts)); currentCommTimeouts.ReadIntervalTimeout = MAXDWORD; if (!::SetCommTimeouts(handle, ¤tCommTimeouts)) { setError(getSystemError()); return false; } const DWORD eventMask = (mode & QIODevice::ReadOnly) ? EV_RXCHAR : 0; if (!::SetCommMask(handle, eventMask)) { setError(getSystemError()); return false; } if ((eventMask & EV_RXCHAR) && !startAsyncCommunication()) return false; return true; } bool QSerialPortPrivate::setDcb(DCB *dcb) { if (!::SetCommState(handle, dcb)) { setError(getSystemError()); return false; } return true; } bool QSerialPortPrivate::getDcb(DCB *dcb) { ::ZeroMemory(dcb, sizeof(DCB)); dcb->DCBlength = sizeof(DCB); if (!::GetCommState(handle, dcb)) { setError(getSystemError()); return false; } return true; } QSerialPortErrorInfo QSerialPortPrivate::getSystemError(int systemErrorCode) const { if (systemErrorCode == -1) systemErrorCode = ::GetLastError(); QSerialPortErrorInfo error; error.errorString = qt_error_string(systemErrorCode); switch (systemErrorCode) { case ERROR_SUCCESS: error.errorCode = QSerialPort::NoError; break; case ERROR_IO_PENDING: error.errorCode = QSerialPort::NoError; break; case ERROR_MORE_DATA: error.errorCode = QSerialPort::NoError; break; case ERROR_FILE_NOT_FOUND: error.errorCode = QSerialPort::DeviceNotFoundError; break; case ERROR_PATH_NOT_FOUND: error.errorCode = QSerialPort::DeviceNotFoundError; break; case ERROR_INVALID_NAME: error.errorCode = QSerialPort::DeviceNotFoundError; break; case ERROR_ACCESS_DENIED: error.errorCode = QSerialPort::PermissionError; break; case ERROR_INVALID_HANDLE: error.errorCode = QSerialPort::ResourceError; break; case ERROR_INVALID_PARAMETER: error.errorCode = QSerialPort::UnsupportedOperationError; break; case ERROR_BAD_COMMAND: error.errorCode = QSerialPort::ResourceError; break; case ERROR_DEVICE_REMOVED: error.errorCode = QSerialPort::ResourceError; break; case ERROR_OPERATION_ABORTED: error.errorCode = QSerialPort::ResourceError; break; case WAIT_TIMEOUT: error.errorCode = QSerialPort::TimeoutError; break; default: error.errorCode = QSerialPort::UnknownError; break; } return error; } // This table contains standard values of baud rates that // are defined in file winbase.h QList QSerialPortPrivate::standardBaudRates() { static const QList baudRates = { CBR_110, CBR_300, CBR_600, CBR_1200, CBR_2400, CBR_4800, CBR_9600, CBR_14400, CBR_19200, CBR_38400, CBR_56000, CBR_57600, CBR_115200, CBR_128000, CBR_256000 }; return baudRates; } QSerialPort::Handle QSerialPort::handle() const { Q_D(const QSerialPort); return d->handle; } void QSerialPortPrivate::ioCompletionRoutine( DWORD errorCode, DWORD bytesTransfered, OVERLAPPED *overlappedBase) { const auto overlapped = static_cast(overlappedBase); if (overlapped->dptr) { overlapped->dptr->handleNotification(bytesTransfered, errorCode, overlappedBase); } else { delete overlapped; } } QT_END_NAMESPACE