| /**************************************************************************** |
| ** |
| ** Copyright (C) 2012 Denis Shienkov <denis.shienkov@gmail.com> |
| ** Copyright (C) 2012 Laszlo Papp <lpapp@kde.org> |
| ** Copyright (C) 2012 Andre Hartmann <aha_1980@gmx.de> |
| ** 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 <QtCore/qcoreevent.h> |
| #include <QtCore/qelapsedtimer.h> |
| #include <QtCore/qmutex.h> |
| #include <QtCore/qtimer.h> |
| #include <QtCore/qvector.h> |
| #include <algorithm> |
| |
| 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<LPOVERLAPPED_COMPLETION_ROUTINE>(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<PIO_STATUS_BLOCK>( |
| &overlapped->Internal); |
| ioStatusBlock->Status = STATUS_PENDING; |
| |
| const NTSTATUS status = ::NtDeviceIoControlFile( |
| deviceHandle, |
| nullptr, |
| qt_apc_routine, |
| reinterpret_cast<PVOID>(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 = resolveNtdllSymbols(&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<const wchar_t*>(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<qint32> QSerialPortPrivate::standardBaudRates() |
| { |
| static const QList<qint32> 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<Overlapped *>(overlappedBase); |
| if (overlapped->dptr) { |
| overlapped->dptr->handleNotification(bytesTransfered, errorCode, |
| overlappedBase); |
| } else { |
| delete overlapped; |
| } |
| } |
| |
| QT_END_NAMESPACE |