source: trunk/tools/runonphone/symbianutils/trkdevice.cpp@ 1147

Last change on this file since 1147 was 846, checked in by Dmitry A. Kuminov, 14 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

File size: 34.9 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation (qt-info@nokia.com)
6**
7** This file is part of the tools applications of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial Usage
11** Licensees holding valid Qt Commercial licenses may use this file in
12** accordance with the Qt Commercial License Agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and Nokia.
15**
16** GNU Lesser General Public License Usage
17** Alternatively, this file may be used under the terms of the GNU Lesser
18** General Public License version 2.1 as published by the Free Software
19** Foundation and appearing in the file LICENSE.LGPL included in the
20** packaging of this file. Please review the following information to
21** ensure the GNU Lesser General Public License version 2.1 requirements
22** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23**
24** In addition, as a special exception, Nokia gives you certain additional
25** rights. These rights are described in the Nokia Qt LGPL Exception
26** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you have questions regarding the use of this file, please contact
37** Nokia at qt-info@nokia.com.
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "trkdevice.h"
43#include "trkutils.h"
44#include "trkutils_p.h"
45
46#include <QtCore/QString>
47#include <QtCore/QDebug>
48#include <QtCore/QQueue>
49#include <QtCore/QHash>
50#include <QtCore/QMap>
51#include <QtCore/QThread>
52#include <QtCore/QMutex>
53#include <QtCore/QWaitCondition>
54#include <QtCore/QSharedPointer>
55#include <QtCore/QScopedPointer>
56#include <QtCore/QMetaType>
57
58#ifdef Q_OS_WIN
59# include <windows.h>
60#else
61# include <QtCore/QFile>
62
63# include <stdio.h>
64# include <sys/ioctl.h>
65# include <sys/types.h>
66# include <termios.h>
67# include <errno.h>
68# include <string.h>
69# include <unistd.h>
70/* Required headers for select() according to POSIX.1-2001 */
71# include <sys/select.h>
72/* Required headers for select() according to earlier standards:
73 #include <sys/time.h>
74 #include <sys/types.h>
75 #include <unistd.h>
76*/
77#endif
78
79#ifdef Q_OS_WIN
80
81// Format windows error from GetLastError() value:
82// TODO: Use the one provided by the utils lib.
83QString winErrorMessage(unsigned long error)
84{
85 QString rc = QString::fromLatin1("#%1: ").arg(error);
86 ushort *lpMsgBuf;
87
88 const int len = FormatMessage(
89 FORMAT_MESSAGE_ALLOCATE_BUFFER
90 | FORMAT_MESSAGE_FROM_SYSTEM
91 | FORMAT_MESSAGE_IGNORE_INSERTS,
92 NULL, error, 0, (LPTSTR)&lpMsgBuf, 0, NULL);
93 if (len) {
94 rc = QString::fromUtf16(lpMsgBuf, len);
95 LocalFree(lpMsgBuf);
96 } else {
97 rc += QString::fromLatin1("<unknown error>");
98 }
99 return rc;
100}
101
102#endif
103
104enum { verboseTrk = 0 };
105
106static inline QString msgAccessingClosedDevice(const QString &msg)
107{
108 return QString::fromLatin1("Error: Attempt to access device '%1', which is closed.").arg(msg);
109}
110
111namespace trk {
112
113///////////////////////////////////////////////////////////////////////
114//
115// TrkMessage
116//
117///////////////////////////////////////////////////////////////////////
118
119/* A message to be send to TRK, triggering a callback on receipt
120 * of the answer. */
121struct TrkMessage
122{
123 explicit TrkMessage(byte code = 0u, byte token = 0u,
124 TrkCallback callback = TrkCallback());
125
126 byte code;
127 byte token;
128 QByteArray data;
129 QVariant cookie;
130 TrkCallback callback;
131};
132
133TrkMessage::TrkMessage(byte c, byte t, TrkCallback cb) :
134 code(c),
135 token(t),
136 callback(cb)
137{
138}
139
140QDebug operator<<(QDebug d, const TrkMessage &msg)
141{
142 return d << "Message: Code: " << msg.code
143 << " Token: " << msg.token << " " << msg.data.toHex();
144}
145
146} // namespace trk
147
148Q_DECLARE_METATYPE(trk::TrkMessage)
149Q_DECLARE_METATYPE(trk::TrkResult)
150
151namespace trk {
152
153///////////////////////////////////////////////////////////////////////
154//
155// TrkWriteQueue: Mixin class that manages a write queue of Trk messages.
156// pendingMessage()/notifyWriteResult() should be called from a worked/timer
157// that writes the messages. The class does not take precautions for multithreading.
158// A no-op message is simply taken off the queue. The calling class
159// can use the helper invokeNoopMessage() to trigger its callback.
160//
161///////////////////////////////////////////////////////////////////////
162
163class TrkWriteQueue
164{
165 Q_DISABLE_COPY(TrkWriteQueue)
166public:
167 explicit TrkWriteQueue();
168 void clear();
169
170 // Enqueue messages.
171 void queueTrkMessage(byte code, TrkCallback callback,
172 const QByteArray &data, const QVariant &cookie);
173 void queueTrkInitialPing();
174
175 // Call this from the device read notification with the results.
176 void slotHandleResult(const TrkResult &result, QMutex *mutex = 0);
177
178 // pendingMessage() can be called periodically in a timer to retrieve
179 // the pending messages to be sent.
180 enum PendingMessageResult {
181 NoMessage, // No message in queue.
182 PendingMessage, /* There is a queued message. The calling class
183 * can write it out and use notifyWriteResult()
184 * to notify about the result. */
185 NoopMessageDequeued // A no-op message has been dequeued. see invokeNoopMessage().
186 };
187
188 PendingMessageResult pendingMessage(TrkMessage *message);
189 // Notify the queue about the success of the write operation
190 // after taking the pendingMessage off.
191 enum WriteResult {
192 WriteOk,
193 WriteFailedDiscard, // Discard failed message
194 WriteFailedKeep, // Keep failed message
195 };
196 void notifyWriteResult(WriteResult ok);
197
198 // Helper function that invokes the callback of a no-op message
199 static void invokeNoopMessage(trk::TrkMessage);
200
201private:
202 typedef QMap<byte, TrkMessage> TokenMessageMap;
203
204 byte nextTrkWriteToken();
205
206 byte m_trkWriteToken;
207 QQueue<TrkMessage> m_trkWriteQueue;
208 TokenMessageMap m_writtenTrkMessages;
209 bool m_trkWriteBusy;
210};
211
212TrkWriteQueue::TrkWriteQueue() :
213 m_trkWriteToken(0),
214 m_trkWriteBusy(false)
215{
216}
217
218void TrkWriteQueue::clear()
219{
220 m_trkWriteToken = 0;
221 m_trkWriteBusy = false;
222 m_trkWriteQueue.clear();
223 const int discarded = m_writtenTrkMessages.size();
224 m_writtenTrkMessages.clear();
225 if (verboseTrk)
226 qDebug() << "TrkWriteQueue::clear: discarded " << discarded;
227}
228
229byte TrkWriteQueue::nextTrkWriteToken()
230{
231 ++m_trkWriteToken;
232 if (m_trkWriteToken == 0)
233 ++m_trkWriteToken;
234 if (verboseTrk)
235 qDebug() << "nextTrkWriteToken:" << m_trkWriteToken;
236 return m_trkWriteToken;
237}
238
239void TrkWriteQueue::queueTrkMessage(byte code, TrkCallback callback,
240 const QByteArray &data, const QVariant &cookie)
241{
242 const byte token = code == TRK_WRITE_QUEUE_NOOP_CODE ?
243 byte(0) : nextTrkWriteToken();
244 TrkMessage msg(code, token, callback);
245 msg.data = data;
246 msg.cookie = cookie;
247 m_trkWriteQueue.append(msg);
248}
249
250TrkWriteQueue::PendingMessageResult TrkWriteQueue::pendingMessage(TrkMessage *message)
251{
252 // Invoked from timer, try to flush out message queue
253 if (m_trkWriteBusy || m_trkWriteQueue.isEmpty())
254 return NoMessage;
255 // Handle the noop message, just invoke CB in slot (ower thread)
256 if (m_trkWriteQueue.front().code == TRK_WRITE_QUEUE_NOOP_CODE) {
257 *message = m_trkWriteQueue.dequeue();
258 return NoopMessageDequeued;
259 }
260 // Insert into map fir answers (as reading threads might get an
261 // answer before notifyWriteResult(true)) is called.
262 *message = m_trkWriteQueue.front();
263 m_writtenTrkMessages.insert(message->token, *message);
264 m_trkWriteBusy = true;
265 return PendingMessage;
266}
267
268void TrkWriteQueue::invokeNoopMessage(trk::TrkMessage noopMessage)
269{
270 TrkResult result;
271 result.code = noopMessage.code;
272 result.token = noopMessage.token;
273 result.data = noopMessage.data;
274 result.cookie = noopMessage.cookie;
275 noopMessage.callback(result);
276}
277
278void TrkWriteQueue::notifyWriteResult(WriteResult wr)
279{
280 // On success, dequeue message and await result
281 const byte token = m_trkWriteQueue.front().token;
282 switch (wr) {
283 case WriteOk:
284 m_trkWriteQueue.dequeue();
285 break;
286 case WriteFailedKeep:
287 case WriteFailedDiscard:
288 m_writtenTrkMessages.remove(token);
289 m_trkWriteBusy = false;
290 if (wr == WriteFailedDiscard)
291 m_trkWriteQueue.dequeue();
292 break;
293 }
294}
295
296void TrkWriteQueue::slotHandleResult(const TrkResult &result, QMutex *mutex)
297{
298 // Find which request the message belongs to and invoke callback
299 // if ACK or on NAK if desired.
300 if (mutex)
301 mutex->lock();
302 m_trkWriteBusy = false;
303 const TokenMessageMap::iterator it = m_writtenTrkMessages.find(result.token);
304 if (it == m_writtenTrkMessages.end()) {
305 if (mutex)
306 mutex->unlock();
307 return;
308 }
309 TrkCallback callback = it.value().callback;
310 const QVariant cookie = it.value().cookie;
311 m_writtenTrkMessages.erase(it);
312 if (mutex)
313 mutex->unlock();
314 // Invoke callback
315 if (callback) {
316 TrkResult result1 = result;
317 result1.cookie = cookie;
318 callback(result1);
319 }
320}
321
322void TrkWriteQueue::queueTrkInitialPing()
323{
324 // Ping, reset sequence count
325 m_trkWriteToken = 0;
326 m_trkWriteQueue.append(TrkMessage(TrkPing, 0));
327}
328
329///////////////////////////////////////////////////////////////////////
330//
331// DeviceContext to be shared between threads
332//
333///////////////////////////////////////////////////////////////////////
334
335struct DeviceContext {
336 DeviceContext();
337#ifdef Q_OS_WIN
338 HANDLE device;
339 OVERLAPPED readOverlapped;
340 OVERLAPPED writeOverlapped;
341#else
342 QFile file;
343#endif
344 bool serialFrame;
345 QMutex mutex;
346};
347
348DeviceContext::DeviceContext() :
349#ifdef Q_OS_WIN
350 device(INVALID_HANDLE_VALUE),
351#endif
352 serialFrame(true)
353{
354}
355
356///////////////////////////////////////////////////////////////////////
357//
358// TrkWriterThread: A thread operating a TrkWriteQueue.
359// with exception of the handling of the TRK_WRITE_QUEUE_NOOP_CODE
360// synchronization message. The invocation of the callback is then
361// done by the thread owning the TrkWriteQueue, while pendingMessage() is called
362// from another thread. This happens via a Qt::BlockingQueuedConnection.
363
364///////////////////////////////////////////////////////////////////////
365
366class WriterThread : public QThread
367{
368 Q_OBJECT
369 Q_DISABLE_COPY(WriterThread)
370public:
371 explicit WriterThread(const QSharedPointer<DeviceContext> &context);
372
373 // Enqueue messages.
374 void queueTrkMessage(byte code, TrkCallback callback,
375 const QByteArray &data, const QVariant &cookie);
376 void queueTrkInitialPing();
377
378 void clearWriteQueue();
379
380 // Call this from the device read notification with the results.
381 void slotHandleResult(const TrkResult &result);
382
383 virtual void run();
384
385signals:
386 void error(const QString &);
387 void internalNoopMessageDequeued(const trk::TrkMessage&);
388
389public slots:
390 bool trkWriteRawMessage(const TrkMessage &msg);
391 void terminate();
392 void tryWrite();
393
394private slots:
395 void invokeNoopMessage(const trk::TrkMessage &);
396
397private:
398 bool write(const QByteArray &data, QString *errorMessage);
399 inline int writePendingMessage();
400
401 const QSharedPointer<DeviceContext> m_context;
402 QMutex m_dataMutex;
403 QMutex m_waitMutex;
404 QWaitCondition m_waitCondition;
405 TrkWriteQueue m_queue;
406 bool m_terminate;
407};
408
409WriterThread::WriterThread(const QSharedPointer<DeviceContext> &context) :
410 m_context(context),
411 m_terminate(false)
412{
413 static const int trkMessageMetaId = qRegisterMetaType<trk::TrkMessage>();
414 Q_UNUSED(trkMessageMetaId)
415 connect(this, SIGNAL(internalNoopMessageDequeued(trk::TrkMessage)),
416 this, SLOT(invokeNoopMessage(trk::TrkMessage)), Qt::BlockingQueuedConnection);
417}
418
419void WriterThread::run()
420{
421 while (writePendingMessage() == 0) ;
422}
423
424int WriterThread::writePendingMessage()
425{
426 enum { MaxAttempts = 100, RetryIntervalMS = 200 };
427
428 // Wait. Use a timeout in case something is already queued before we
429 // start up or some weird hanging exit condition
430 m_waitMutex.lock();
431 m_waitCondition.wait(&m_waitMutex, 100);
432 m_waitMutex.unlock();
433 if (m_terminate)
434 return 1;
435
436 // Send off message
437 m_dataMutex.lock();
438 TrkMessage message;
439 const TrkWriteQueue::PendingMessageResult pr = m_queue.pendingMessage(&message);
440 m_dataMutex.unlock();
441
442 switch (pr) {
443 case TrkWriteQueue::NoMessage:
444 break;
445 case TrkWriteQueue::PendingMessage: {
446 //qDebug() << "Write pending message " << message;
447 // Untested: try to re-send a few times
448 bool success = false;
449 for (int r = 0; !success && (r < MaxAttempts); r++) {
450 success = trkWriteRawMessage(message);
451 if (!success) {
452 emit error(QString::fromLatin1("Write failure, attempt %1 of %2.").arg(r).arg(int(MaxAttempts)));
453 if (m_terminate)
454 return 1;
455 QThread::msleep(RetryIntervalMS);
456 }
457 }
458 // Notify queue. If still failed, give up.
459 m_dataMutex.lock();
460 m_queue.notifyWriteResult(success ? TrkWriteQueue::WriteOk : TrkWriteQueue::WriteFailedDiscard);
461 m_dataMutex.unlock();
462 }
463 break;
464 case TrkWriteQueue::NoopMessageDequeued:
465 // Sync with thread that owns us via a blocking signal
466 if (verboseTrk)
467 qDebug() << "Noop message dequeued" << message;
468 emit internalNoopMessageDequeued(message);
469 break;
470 } // switch
471 return 0;
472}
473
474void WriterThread::invokeNoopMessage(const trk::TrkMessage &msg)
475{
476 TrkWriteQueue::invokeNoopMessage(msg);
477}
478
479void WriterThread::terminate()
480{
481 m_terminate = true;
482 m_waitCondition.wakeAll();
483 wait();
484 m_terminate = false;
485 m_queue.clear();
486}
487
488#ifdef Q_OS_WIN
489
490static inline QString msgTerminated(int size)
491{
492 return QString::fromLatin1("Terminated with %1 bytes pending.").arg(size);
493}
494
495// Interruptible synchronous write function.
496static inline bool overlappedSyncWrite(HANDLE file,
497 const bool &terminateFlag,
498 const char *data,
499 DWORD size, DWORD *charsWritten,
500 OVERLAPPED *overlapped,
501 QString *errorMessage)
502{
503 if (WriteFile(file, data, size, charsWritten, overlapped))
504 return true;
505 const DWORD writeError = GetLastError();
506 if (writeError != ERROR_IO_PENDING) {
507 *errorMessage = QString::fromLatin1("WriteFile failed: %1").arg(winErrorMessage(writeError));
508 return false;
509 }
510 // Wait for written or thread terminated
511 const DWORD timeoutMS = 200;
512 const unsigned maxAttempts = 20;
513 DWORD wr = WaitForSingleObject(overlapped->hEvent, timeoutMS);
514 for (unsigned n = 0; wr == WAIT_TIMEOUT && n < maxAttempts && !terminateFlag;
515 wr = WaitForSingleObject(overlapped->hEvent, timeoutMS), n++);
516 if (terminateFlag) {
517 *errorMessage = msgTerminated(size);
518 return false;
519 }
520 switch (wr) {
521 case WAIT_OBJECT_0:
522 break;
523 case WAIT_TIMEOUT:
524 *errorMessage = QString::fromLatin1("Write timed out.");
525 return false;
526 default:
527 *errorMessage = QString::fromLatin1("Error while waiting for WriteFile results: %1").arg(winErrorMessage(GetLastError()));
528 return false;
529 }
530 if (!GetOverlappedResult(file, overlapped, charsWritten, TRUE)) {
531 *errorMessage = QString::fromLatin1("Error writing %1 bytes: %2").arg(size).arg(winErrorMessage(GetLastError()));
532 return false;
533 }
534 return true;
535}
536#endif
537
538bool WriterThread::write(const QByteArray &data, QString *errorMessage)
539{
540 if (verboseTrk)
541 qDebug() << "Write raw data: " << stringFromArray(data).toLatin1();
542 QMutexLocker locker(&m_context->mutex);
543#ifdef Q_OS_WIN
544 DWORD charsWritten;
545 if (!overlappedSyncWrite(m_context->device, m_terminate, data.data(), data.size(), &charsWritten, &m_context->writeOverlapped, errorMessage)) {
546 return false;
547 }
548 FlushFileBuffers(m_context->device);
549 return true;
550#else
551 if (m_context->file.write(data) == -1 || !m_context->file.flush()) {
552 *errorMessage = QString::fromLatin1("Cannot write: %1").arg(m_context->file.errorString());
553 return false;
554 }
555 return true;
556#endif
557}
558
559bool WriterThread::trkWriteRawMessage(const TrkMessage &msg)
560{
561 const QByteArray ba = frameMessage(msg.code, msg.token, msg.data, m_context->serialFrame);
562 QString errorMessage;
563 const bool rc = write(ba, &errorMessage);
564 if (!rc) {
565 qWarning("%s\n", qPrintable(errorMessage));
566 emit error(errorMessage);
567 }
568 return rc;
569}
570
571void WriterThread::tryWrite()
572{
573 m_waitCondition.wakeAll();
574}
575
576void WriterThread::queueTrkMessage(byte code, TrkCallback callback,
577 const QByteArray &data, const QVariant &cookie)
578{
579 m_dataMutex.lock();
580 m_queue.queueTrkMessage(code, callback, data, cookie);
581 m_dataMutex.unlock();
582 tryWrite();
583}
584
585void WriterThread::clearWriteQueue()
586{
587 m_dataMutex.lock();
588 m_queue.clear();
589 m_dataMutex.unlock();
590}
591
592void WriterThread::queueTrkInitialPing()
593{
594 m_dataMutex.lock();
595 m_queue.queueTrkInitialPing();
596 m_dataMutex.unlock();
597 tryWrite();
598}
599
600// Call this from the device read notification with the results.
601void WriterThread::slotHandleResult(const TrkResult &result)
602{
603 m_queue.slotHandleResult(result, &m_dataMutex);
604 tryWrite(); // Have messages been enqueued in-between?
605}
606
607
608///////////////////////////////////////////////////////////////////////
609//
610// ReaderThreadBase: Base class for a thread that reads data from
611// the device, decodes the messages and emit signals for the messages.
612// A Qt::BlockingQueuedConnection should be used for the message signal
613// to ensure messages are processed in the correct sequence.
614//
615///////////////////////////////////////////////////////////////////////
616
617class ReaderThreadBase : public QThread
618{
619 Q_OBJECT
620 Q_DISABLE_COPY(ReaderThreadBase)
621public:
622
623 int bytesPending() const { return m_trkReadBuffer.size(); }
624
625signals:
626 void messageReceived(const trk::TrkResult &result, const QByteArray &rawData);
627
628protected:
629 explicit ReaderThreadBase(const QSharedPointer<DeviceContext> &context);
630 void processData(const QByteArray &a);
631 void processData(char c);
632
633 const QSharedPointer<DeviceContext> m_context;
634
635private:
636 void readMessages();
637
638 QByteArray m_trkReadBuffer;
639 bool linkEstablishmentMode;
640};
641
642ReaderThreadBase::ReaderThreadBase(const QSharedPointer<DeviceContext> &context) :
643 m_context(context), linkEstablishmentMode(true)
644{
645 static const int trkResultMetaId = qRegisterMetaType<trk::TrkResult>();
646 Q_UNUSED(trkResultMetaId)
647}
648
649void ReaderThreadBase::processData(const QByteArray &a)
650{
651 m_trkReadBuffer += a;
652 readMessages();
653}
654
655void ReaderThreadBase::processData(char c)
656{
657 m_trkReadBuffer += c;
658 if (m_trkReadBuffer.size() > 1)
659 readMessages();
660}
661
662void ReaderThreadBase::readMessages()
663{
664 TrkResult r;
665 QByteArray rawData;
666 while (extractResult(&m_trkReadBuffer, m_context->serialFrame, &r, linkEstablishmentMode, &rawData)) {
667 emit messageReceived(r, rawData);
668 }
669}
670
671#ifdef Q_OS_WIN
672///////////////////////////////////////////////////////////////////////
673//
674// WinReaderThread: A thread reading from the device using Windows API.
675// Waits on an overlapped I/O handle and an event that tells the thread to
676// terminate.
677//
678///////////////////////////////////////////////////////////////////////
679
680class WinReaderThread : public ReaderThreadBase
681{
682 Q_OBJECT
683 Q_DISABLE_COPY(WinReaderThread)
684public:
685 explicit WinReaderThread(const QSharedPointer<DeviceContext> &context);
686 ~WinReaderThread();
687
688 virtual void run();
689
690signals:
691 void error(const QString &);
692
693public slots:
694 void terminate();
695
696private:
697 enum Handles { FileHandle, TerminateEventHandle, HandleCount };
698
699 inline int tryRead();
700
701 HANDLE m_handles[HandleCount];
702};
703
704WinReaderThread::WinReaderThread(const QSharedPointer<DeviceContext> &context) :
705 ReaderThreadBase(context)
706{
707 m_handles[FileHandle] = NULL;
708 m_handles[TerminateEventHandle] = CreateEvent(NULL, FALSE, FALSE, NULL);
709}
710
711WinReaderThread::~WinReaderThread()
712{
713 CloseHandle(m_handles[TerminateEventHandle]);
714}
715
716// Return 0 to continue or error code
717int WinReaderThread::tryRead()
718{
719 enum { BufSize = 1024 };
720 char buffer[BufSize];
721 // Check if there are already bytes waiting. If not, wait for first byte
722 COMSTAT comStat;
723 if (!ClearCommError(m_context->device, NULL, &comStat)){
724 emit error(QString::fromLatin1("ClearCommError failed: %1").arg(winErrorMessage(GetLastError())));
725 return -7;
726 }
727 const DWORD bytesToRead = qMax(DWORD(1), qMin(comStat.cbInQue, DWORD(BufSize)));
728 // Trigger read
729 DWORD bytesRead = 0;
730 if (ReadFile(m_context->device, &buffer, bytesToRead, &bytesRead, &m_context->readOverlapped)) {
731 if (bytesRead == 1) {
732 processData(buffer[0]);
733 } else {
734 processData(QByteArray(buffer, bytesRead));
735 }
736 return 0;
737 }
738 const DWORD readError = GetLastError();
739 if (readError != ERROR_IO_PENDING) {
740 emit error(QString::fromLatin1("Read error: %1").arg(winErrorMessage(readError)));
741 return -1;
742 }
743 // Wait for either termination or data
744 const DWORD wr = WaitForMultipleObjects(HandleCount, m_handles, false, INFINITE);
745 if (wr == WAIT_FAILED) {
746 emit error(QString::fromLatin1("Wait failed: %1").arg(winErrorMessage(GetLastError())));
747 return -2;
748 }
749 if (wr - WAIT_OBJECT_0 == TerminateEventHandle) {
750 return 1; // Terminate
751 }
752 // Check data
753 if (!GetOverlappedResult(m_context->device, &m_context->readOverlapped, &bytesRead, true)) {
754 emit error(QString::fromLatin1("GetOverlappedResult failed: %1").arg(winErrorMessage(GetLastError())));
755 return -3;
756 }
757 if (bytesRead == 1) {
758 processData(buffer[0]);
759 } else {
760 processData(QByteArray(buffer, bytesRead));
761 }
762 return 0;
763}
764
765void WinReaderThread::run()
766{
767 m_handles[FileHandle] = m_context->readOverlapped.hEvent;
768 while ( tryRead() == 0) ;
769}
770
771void WinReaderThread::terminate()
772{
773 SetEvent(m_handles[TerminateEventHandle]);
774 wait();
775}
776
777typedef WinReaderThread ReaderThread;
778
779#else
780
781///////////////////////////////////////////////////////////////////////
782//
783// UnixReaderThread: A thread reading from the device.
784// Uses select() to wait and a special ioctl() to find out the number
785// of bytes queued. For clean termination, the self-pipe trick is used.
786// The class maintains a pipe, on whose read end the select waits besides
787// the device file handle. To terminate, a byte is written to the pipe.
788//
789///////////////////////////////////////////////////////////////////////
790
791static inline QString msgUnixCallFailedErrno(const char *func, int errorNumber)
792{
793 return QString::fromLatin1("Call to %1() failed: %2").arg(QLatin1String(func), QString::fromLocal8Bit(strerror(errorNumber)));
794}
795
796class UnixReaderThread : public ReaderThreadBase {
797 Q_OBJECT
798 Q_DISABLE_COPY(UnixReaderThread)
799public:
800 explicit UnixReaderThread(const QSharedPointer<DeviceContext> &context);
801 ~UnixReaderThread();
802
803 virtual void run();
804
805signals:
806 void error(const QString &);
807
808public slots:
809 void terminate();
810
811private:
812 inline int tryRead();
813
814 int m_terminatePipeFileDescriptors[2];
815};
816
817UnixReaderThread::UnixReaderThread(const QSharedPointer<DeviceContext> &context) :
818 ReaderThreadBase(context)
819{
820 m_terminatePipeFileDescriptors[0] = m_terminatePipeFileDescriptors[1] = -1;
821 // Set up pipes for termination. Should not fail
822 if (pipe(m_terminatePipeFileDescriptors) < 0)
823 qWarning("%s\n", qPrintable(msgUnixCallFailedErrno("pipe", errno)));
824}
825
826UnixReaderThread::~UnixReaderThread()
827{
828 close(m_terminatePipeFileDescriptors[0]);
829 close(m_terminatePipeFileDescriptors[1]);
830}
831
832int UnixReaderThread::tryRead()
833{
834 fd_set readSet, tempReadSet, tempExceptionSet;
835 struct timeval timeOut;
836 const int fileDescriptor = m_context->file.handle();
837 FD_ZERO(&readSet);
838 FD_SET(fileDescriptor, &readSet);
839 FD_SET(m_terminatePipeFileDescriptors[0], &readSet);
840 const int maxFileDescriptor = qMax(m_terminatePipeFileDescriptors[0], fileDescriptor);
841 int result = 0;
842 do {
843 memcpy(&tempReadSet, &readSet, sizeof(fd_set));
844 memcpy(&tempExceptionSet, &readSet, sizeof(fd_set));
845 timeOut.tv_sec = 1;
846 timeOut.tv_usec = 0;
847 result = select(maxFileDescriptor + 1, &tempReadSet, NULL, &tempExceptionSet, &timeOut);
848 } while ( result < 0 && errno == EINTR );
849 // Timeout?
850 if (result == 0)
851 return 0;
852 // Something wrong?
853 if (result < 0) {
854 emit error(msgUnixCallFailedErrno("select", errno));
855 return -1;
856 }
857 // Did the exception set trigger on the device?
858 if (FD_ISSET(fileDescriptor,&tempExceptionSet)) {
859 emit error(QLatin1String("An Exception occurred on the device."));
860 return -2;
861 }
862 // Check termination pipe.
863 if (FD_ISSET(m_terminatePipeFileDescriptors[0], &tempReadSet)
864 || FD_ISSET(m_terminatePipeFileDescriptors[0], &tempExceptionSet))
865 return 1;
866
867 // determine number of pending bytes and read
868 int numBytes;
869 if (ioctl(fileDescriptor, FIONREAD, &numBytes) < 0) {
870 emit error(msgUnixCallFailedErrno("ioctl", errno));
871 return -1;
872 }
873 m_context->mutex.lock();
874 const QByteArray data = m_context->file.read(numBytes);
875 m_context->mutex.unlock();
876 processData(data);
877 return 0;
878}
879
880void UnixReaderThread::run()
881{
882 // Read loop
883 while (tryRead() == 0)
884 ;
885}
886
887void UnixReaderThread::terminate()
888{
889 // Trigger select() by writing to the pipe
890 char c = 0;
891 const int written = write(m_terminatePipeFileDescriptors[1], &c, 1);
892 Q_UNUSED(written)
893 wait();
894}
895
896typedef UnixReaderThread ReaderThread;
897
898#endif
899
900///////////////////////////////////////////////////////////////////////
901//
902// TrkDevicePrivate
903//
904///////////////////////////////////////////////////////////////////////
905
906struct TrkDevicePrivate
907{
908 TrkDevicePrivate();
909
910 QSharedPointer<DeviceContext> deviceContext;
911 QScopedPointer<WriterThread> writerThread;
912 QScopedPointer<ReaderThread> readerThread;
913
914 QByteArray trkReadBuffer;
915 int verbose;
916 QString errorString;
917 QString port;
918};
919
920///////////////////////////////////////////////////////////////////////
921//
922// TrkDevice
923//
924///////////////////////////////////////////////////////////////////////
925
926TrkDevicePrivate::TrkDevicePrivate() :
927 deviceContext(new DeviceContext),
928 verbose(0)
929{
930}
931
932///////////////////////////////////////////////////////////////////////
933//
934// TrkDevice
935//
936///////////////////////////////////////////////////////////////////////
937
938TrkDevice::TrkDevice(QObject *parent) :
939 QObject(parent),
940 d(new TrkDevicePrivate)
941{}
942
943TrkDevice::~TrkDevice()
944{
945 close();
946 delete d;
947}
948
949bool TrkDevice::open(QString *errorMessage)
950{
951 if (d->verbose || verboseTrk)
952 qDebug() << "Opening" << port() << "is open: " << isOpen() << " serialFrame=" << serialFrame();
953 if (isOpen())
954 return true;
955 if (d->port.isEmpty()) {
956 *errorMessage = QLatin1String("Internal error: No port set on TrkDevice");
957 return false;
958 }
959#ifdef Q_OS_WIN
960 const QString fullPort = QLatin1String("\\\\.\\") + d->port;
961 d->deviceContext->device = CreateFile(reinterpret_cast<const WCHAR*>(fullPort.utf16()),
962 GENERIC_READ | GENERIC_WRITE,
963 0,
964 NULL,
965 OPEN_EXISTING,
966 FILE_ATTRIBUTE_NORMAL|FILE_FLAG_NO_BUFFERING|FILE_FLAG_OVERLAPPED,
967 NULL);
968
969 if (INVALID_HANDLE_VALUE == d->deviceContext->device) {
970 *errorMessage = QString::fromLatin1("Could not open device '%1': %2").arg(port(), winErrorMessage(GetLastError()));
971 return false;
972 }
973 memset(&d->deviceContext->readOverlapped, 0, sizeof(OVERLAPPED));
974 d->deviceContext->readOverlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
975 memset(&d->deviceContext->writeOverlapped, 0, sizeof(OVERLAPPED));
976 d->deviceContext->writeOverlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
977 if (d->deviceContext->readOverlapped.hEvent == NULL || d->deviceContext->writeOverlapped.hEvent == NULL) {
978 *errorMessage = QString::fromLatin1("Failed to create events: %1").arg(winErrorMessage(GetLastError()));
979 return false;
980 }
981#else
982 d->deviceContext->file.setFileName(d->port);
983 if (!d->deviceContext->file.open(QIODevice::ReadWrite|QIODevice::Unbuffered)) {
984 *errorMessage = QString::fromLatin1("Cannot open %1: %2").arg(d->port, d->deviceContext->file.errorString());
985 return false;
986 }
987
988 struct termios termInfo;
989 if (tcgetattr(d->deviceContext->file.handle(), &termInfo) < 0) {
990 *errorMessage = QString::fromLatin1("Unable to retrieve terminal settings: %1 %2").arg(errno).arg(QString::fromAscii(strerror(errno)));
991 return false;
992 }
993 // Turn off terminal echo as not get messages back, among other things
994 termInfo.c_cflag |= CREAD|CLOCAL;
995 termInfo.c_lflag &= (~(ICANON|ECHO|ECHOE|ECHOK|ECHONL|ISIG));
996 termInfo.c_iflag &= (~(INPCK|IGNPAR|PARMRK|ISTRIP|ICRNL|IXANY));
997 termInfo.c_oflag &= (~OPOST);
998 termInfo.c_cc[VMIN] = 0;
999 termInfo.c_cc[VINTR] = _POSIX_VDISABLE;
1000 termInfo.c_cc[VQUIT] = _POSIX_VDISABLE;
1001 termInfo.c_cc[VSTART] = _POSIX_VDISABLE;
1002 termInfo.c_cc[VSTOP] = _POSIX_VDISABLE;
1003 termInfo.c_cc[VSUSP] = _POSIX_VDISABLE;
1004 if (tcsetattr(d->deviceContext->file.handle(), TCSAFLUSH, &termInfo) < 0) {
1005 *errorMessage = QString::fromLatin1("Unable to apply terminal settings: %1 %2").arg(errno).arg(QString::fromAscii(strerror(errno)));
1006 return false;
1007 }
1008#endif
1009 d->readerThread.reset(new ReaderThread(d->deviceContext));
1010 connect(d->readerThread.data(), SIGNAL(error(QString)), this, SLOT(emitError(QString)),
1011 Qt::QueuedConnection);
1012 connect(d->readerThread.data(), SIGNAL(messageReceived(trk::TrkResult,QByteArray)),
1013 this, SLOT(slotMessageReceived(trk::TrkResult,QByteArray)),
1014 Qt::QueuedConnection);
1015 d->readerThread->start();
1016
1017 d->writerThread.reset(new WriterThread(d->deviceContext));
1018 connect(d->writerThread.data(), SIGNAL(error(QString)), this, SLOT(emitError(QString)),
1019 Qt::QueuedConnection);
1020 d->writerThread->start();
1021
1022 if (d->verbose || verboseTrk)
1023 qDebug() << "Opened" << d->port << d->readerThread.data() << d->writerThread.data();
1024 return true;
1025}
1026
1027void TrkDevice::close()
1028{
1029 if (verboseTrk)
1030 qDebug() << "close" << d->port << " is open: " << isOpen()
1031 << " read pending " << (d->readerThread.isNull() ? 0 : d->readerThread->bytesPending())
1032 << sender();
1033 if (!isOpen())
1034 return;
1035 if (d->readerThread)
1036 d->readerThread->terminate();
1037 if (d->writerThread)
1038 d->writerThread->terminate();
1039#ifdef Q_OS_WIN
1040 CloseHandle(d->deviceContext->device);
1041 d->deviceContext->device = INVALID_HANDLE_VALUE;
1042 CloseHandle(d->deviceContext->readOverlapped.hEvent);
1043 CloseHandle(d->deviceContext->writeOverlapped.hEvent);
1044 d->deviceContext->readOverlapped.hEvent = d->deviceContext->writeOverlapped.hEvent = NULL;
1045#else
1046 d->deviceContext->file.close();
1047#endif
1048
1049 if (d->verbose)
1050 emitLogMessage("Close");
1051}
1052
1053bool TrkDevice::isOpen() const
1054{
1055#ifdef Q_OS_WIN
1056 return d->deviceContext->device != INVALID_HANDLE_VALUE;
1057#else
1058 return d->deviceContext->file.isOpen();
1059#endif
1060}
1061
1062QString TrkDevice::port() const
1063{
1064 return d->port;
1065}
1066
1067void TrkDevice::setPort(const QString &p)
1068{
1069 if (verboseTrk)
1070 qDebug() << "setPort" << p;
1071 d->port = p;
1072}
1073
1074QString TrkDevice::errorString() const
1075{
1076 return d->errorString;
1077}
1078
1079bool TrkDevice::serialFrame() const
1080{
1081 return d->deviceContext->serialFrame;
1082}
1083
1084void TrkDevice::setSerialFrame(bool f)
1085{
1086 if (verboseTrk)
1087 qDebug() << "setSerialFrame" << f;
1088 d->deviceContext->serialFrame = f;
1089}
1090
1091int TrkDevice::verbose() const
1092{
1093 return d->verbose;
1094}
1095
1096void TrkDevice::setVerbose(int b)
1097{
1098 d->verbose = b;
1099}
1100
1101void TrkDevice::slotMessageReceived(const trk::TrkResult &result, const QByteArray &rawData)
1102{
1103 if (isOpen()) { // Might receive bytes after closing due to queued connections.
1104 d->writerThread->slotHandleResult(result);
1105 if (d->verbose > 1)
1106 qDebug() << "Received: " << result.toString();
1107 emit messageReceived(result);
1108 if (!rawData.isEmpty())
1109 emit rawDataReceived(rawData);
1110 }
1111}
1112
1113void TrkDevice::emitError(const QString &s)
1114{
1115 d->errorString = s;
1116 qWarning("%s\n", qPrintable(s));
1117 emit error(s);
1118}
1119
1120void TrkDevice::clearWriteQueue()
1121{
1122 if (isOpen())
1123 d->writerThread->clearWriteQueue();
1124}
1125
1126void TrkDevice::sendTrkMessage(byte code, TrkCallback callback,
1127 const QByteArray &data, const QVariant &cookie)
1128{
1129 if (!isOpen()) {
1130 emitError(msgAccessingClosedDevice(d->port));
1131 return;
1132 }
1133 if (!d->writerThread.isNull()) {
1134 if (d->verbose > 1) {
1135 QByteArray msg = "Sending: 0x";
1136 msg += QByteArray::number(code, 16);
1137 msg += ": ";
1138 msg += stringFromArray(data).toLatin1();
1139 if (cookie.isValid())
1140 msg += " Cookie: " + cookie.toString().toLatin1();
1141 qDebug("%s", msg.data());
1142 }
1143 d->writerThread->queueTrkMessage(code, callback, data, cookie);
1144 }
1145}
1146
1147void TrkDevice::sendTrkInitialPing()
1148{
1149 if (!isOpen()) {
1150 emitError(msgAccessingClosedDevice(d->port));
1151 return;
1152 }
1153 if (!d->writerThread.isNull())
1154 d->writerThread->queueTrkInitialPing();
1155}
1156
1157bool TrkDevice::sendTrkAck(byte token)
1158{
1159 if (!isOpen()) {
1160 emitError(msgAccessingClosedDevice(d->port));
1161 return false;
1162 }
1163 if (d->writerThread.isNull())
1164 return false;
1165 // The acknowledgement must not be queued!
1166 TrkMessage msg(0x80, token);
1167 msg.token = token;
1168 msg.data.append('\0');
1169 if (verboseTrk)
1170 qDebug() << "Write synchroneous message: " << msg;
1171 return d->writerThread->trkWriteRawMessage(msg);
1172 // 01 90 00 07 7e 80 01 00 7d 5e 7e
1173}
1174
1175void TrkDevice::emitLogMessage(const QString &msg)
1176{
1177 if (d->verbose)
1178 qDebug("%s\n", qPrintable(msg));
1179 emit logMessage(msg);
1180}
1181
1182} // namespace trk
1183
1184#include "trkdevice.moc"
Note: See TracBrowser for help on using the repository browser.