diff options
Diffstat (limited to 'tools/runonphone/symbianutils/symbiandevicemanager.cpp')
-rw-r--r-- | tools/runonphone/symbianutils/symbiandevicemanager.cpp | 290 |
1 files changed, 272 insertions, 18 deletions
diff --git a/tools/runonphone/symbianutils/symbiandevicemanager.cpp b/tools/runonphone/symbianutils/symbiandevicemanager.cpp index 02727d7..23f5348 100644 --- a/tools/runonphone/symbianutils/symbiandevicemanager.cpp +++ b/tools/runonphone/symbianutils/symbiandevicemanager.cpp @@ -41,7 +41,11 @@ #include "symbiandevicemanager.h" #include "trkdevice.h" +#include "codadevice.h" +#include "virtualserialdevice.h" +#include <QtCore/QCoreApplication> +#include <QtCore/QEvent> #include <QtCore/QSettings> #include <QtCore/QStringList> #include <QtCore/QFileInfo> @@ -50,6 +54,9 @@ #include <QtCore/QSharedData> #include <QtCore/QScopedPointer> #include <QtCore/QSignalMapper> +#include <QtCore/QThread> +#include <QtCore/QWaitCondition> +#include <QtCore/QTimer> namespace SymbianUtils { @@ -66,7 +73,7 @@ public: SymbianDeviceData(); ~SymbianDeviceData(); - inline bool isOpen() const { return !device.isNull() && device->isOpen(); } + bool isOpen() const; void forcedClose(); QString portName; @@ -77,15 +84,25 @@ public: DeviceCommunicationType type; QSharedPointer<trk::TrkDevice> device; - bool deviceAcquired; + QSharedPointer<Coda::CodaDevice> codaDevice; + int deviceAcquired; }; SymbianDeviceData::SymbianDeviceData() : type(SerialPortCommunication), - deviceAcquired(false) + deviceAcquired(0) { } +bool SymbianDeviceData::isOpen() const +{ + if (device) + return device->isOpen(); + if (codaDevice) + return codaDevice->device()->isOpen(); + return false; +} + SymbianDeviceData::~SymbianDeviceData() { forcedClose(); @@ -101,7 +118,10 @@ void SymbianDeviceData::forcedClose() if (deviceAcquired) qWarning("Device on '%s' unplugged while an operation is in progress.", qPrintable(portName)); - device->close(); + if (device) + device->close(); + else + codaDevice->device()->close(); } } @@ -167,7 +187,7 @@ SymbianDevice::TrkDevicePtr SymbianDevice::acquireDevice() m_data->device->setPort(m_data->portName); m_data->device->setSerialFrame(m_data->type == SerialPortCommunication); } - m_data->deviceAcquired = true; + m_data->deviceAcquired = 1; return m_data->device; } @@ -184,7 +204,7 @@ void SymbianDevice::releaseDevice(TrkDevicePtr *ptr /* = 0 */) ptr->data()->disconnect(); *ptr = TrkDevicePtr(); } - m_data->deviceAcquired = false; + m_data->deviceAcquired = 0; } else { qWarning("Internal error: Attempt to release device that is not acquired."); } @@ -255,17 +275,34 @@ SYMBIANUTILS_EXPORT QDebug operator<<(QDebug d, const SymbianDevice &cd) // ------------- SymbianDeviceManagerPrivate struct SymbianDeviceManagerPrivate { - SymbianDeviceManagerPrivate() : m_initialized(false), m_destroyReleaseMapper(0) {} + SymbianDeviceManagerPrivate() : m_initialized(false), m_devicesLock(QMutex::Recursive) {} bool m_initialized; SymbianDeviceManager::SymbianDeviceList m_devices; - QSignalMapper *m_destroyReleaseMapper; + QMutex m_devicesLock; // Used for protecting access to m_devices and serialising getCodaDevice/delayedClosePort + // The following 2 variables are needed to manage requests for a TCF port not coming from the main thread + int m_constructTcfPortEventType; + QMutex m_codaPortWaitMutex; +}; + +class QConstructTcfPortEvent : public QEvent +{ +public: + QConstructTcfPortEvent(QEvent::Type eventId, const QString &portName, CodaDevicePtr *device, QWaitCondition *waiter) : + QEvent(eventId), m_portName(portName), m_device(device), m_waiter(waiter) + {} + + QString m_portName; + CodaDevicePtr* m_device; + QWaitCondition *m_waiter; }; + SymbianDeviceManager::SymbianDeviceManager(QObject *parent) : QObject(parent), d(new SymbianDeviceManagerPrivate) { + d->m_constructTcfPortEventType = QEvent::registerEventType(); } SymbianDeviceManager::~SymbianDeviceManager() @@ -276,11 +313,13 @@ SymbianDeviceManager::~SymbianDeviceManager() SymbianDeviceManager::SymbianDeviceList SymbianDeviceManager::devices() const { ensureInitialized(); + QMutexLocker lock(&d->m_devicesLock); return d->m_devices; } QString SymbianDeviceManager::toString() const { + QMutexLocker lock(&d->m_devicesLock); QString rc; QTextStream str(&rc); str << d->m_devices.size() << " devices:\n"; @@ -305,6 +344,7 @@ int SymbianDeviceManager::findByPortName(const QString &p) const QString SymbianDeviceManager::friendlyNameForPort(const QString &port) const { + QMutexLocker lock(&d->m_devicesLock); const int idx = findByPortName(port); return idx == -1 ? QString() : d->m_devices.at(idx).friendlyName(); } @@ -326,6 +366,106 @@ SymbianDeviceManager::TrkDevicePtr return rc; } +CodaDevicePtr SymbianDeviceManager::getCodaDevice(const QString &port) +{ + ensureInitialized(); + QMutexLocker lock(&d->m_devicesLock); + const int idx = findByPortName(port); + if (idx == -1) { + qWarning("Attempt to acquire device '%s' that does not exist.", qPrintable(port)); + if (debug) + qDebug() << *this; + return CodaDevicePtr(); + } + SymbianDevice& device = d->m_devices[idx]; + if (device.m_data->device && device.m_data->device.data()->isOpen()) { + qWarning("Attempting to open a port '%s' that is configured for TRK!", qPrintable(port)); + return CodaDevicePtr(); + } + CodaDevicePtr& devicePtr = device.m_data->codaDevice; + if (devicePtr.isNull() || !devicePtr->device()->isOpen()) { + // Check we instanciate in the correct thread - we can't afford to create the CodaDevice (and more specifically, open the VirtualSerialDevice) in a thread that isn't guaranteed to be long-lived. + // Therefore, if we're not in SymbianDeviceManager's thread, rejig things so it's opened in the main thread + if (QThread::currentThread() != thread()) { + // SymbianDeviceManager is owned by the main thread + d->m_codaPortWaitMutex.lock(); + QWaitCondition waiter; + QCoreApplication::postEvent(this, new QConstructTcfPortEvent((QEvent::Type)d->m_constructTcfPortEventType, port, &devicePtr, &waiter)); + waiter.wait(&d->m_codaPortWaitMutex); + // When the wait returns (due to the wakeAll in SymbianDeviceManager::customEvent), the CodaDevice will be fully set up + d->m_codaPortWaitMutex.unlock(); + } else { + // We're in the main thread, just set it up directly + constructCodaPort(devicePtr, port); + } + // We still carry on in the case we failed to open so the client can access the IODevice's errorString() + } + if (devicePtr->device()->isOpen()) + device.m_data->deviceAcquired++; + return devicePtr; +} + +void SymbianDeviceManager::constructCodaPort(CodaDevicePtr& device, const QString& portName) +{ + QMutexLocker locker(&d->m_codaPortWaitMutex); + if (device.isNull()) { + device = QSharedPointer<Coda::CodaDevice>(new Coda::CodaDevice); + const QSharedPointer<SymbianUtils::VirtualSerialDevice> serialDevice(new SymbianUtils::VirtualSerialDevice(portName)); + device->setSerialFrame(true); + device->setDevice(serialDevice); + } + if (!device->device()->isOpen()) { + bool ok = device->device().staticCast<SymbianUtils::VirtualSerialDevice>()->open(QIODevice::ReadWrite); + if (!ok && debug) { + qDebug("SymbianDeviceManager: Failed to open port %s", qPrintable(portName)); + } + } +} + +void SymbianDeviceManager::customEvent(QEvent *event) +{ + if (event->type() == d->m_constructTcfPortEventType) { + QConstructTcfPortEvent* constructEvent = static_cast<QConstructTcfPortEvent*>(event); + constructCodaPort(*constructEvent->m_device, constructEvent->m_portName); + constructEvent->m_waiter->wakeAll(); // Should only ever be one thing waiting on this + } +} + +void SymbianDeviceManager::releaseCodaDevice(CodaDevicePtr &port) +{ + if (port) { + QMutexLocker(&d->m_devicesLock); + // Check if this was the last reference to the port, if so close it after a short delay + foreach (const SymbianDevice& device, d->m_devices) { + if (device.m_data->codaDevice.data() == port.data()) { + if (device.m_data->deviceAcquired > 0) + device.m_data->deviceAcquired--; + if (device.m_data->deviceAcquired == 0) { + if (debug) + qDebug("Starting timer to close port %s", qPrintable(device.m_data->portName)); + QTimer::singleShot(1000, this, SLOT(delayedClosePort())); + } + break; + } + } + port.clear(); + } +} + +void SymbianDeviceManager::delayedClosePort() +{ + // Find any coda ports that are still open but have a reference count of zero, and delete them + QMutexLocker(&d->m_devicesLock); + foreach (const SymbianDevice& device, d->m_devices) { + Coda::CodaDevice* codaDevice = device.m_data->codaDevice.data(); + if (codaDevice && device.m_data->deviceAcquired == 0 && codaDevice->device()->isOpen()) { + if (debug) + qDebug("Closing device %s", qPrintable(device.m_data->portName)); + device.m_data->codaDevice->device()->close(); + } + } +} + void SymbianDeviceManager::update() { update(true); @@ -336,11 +476,10 @@ void SymbianDeviceManager::releaseDevice(const QString &port) const int idx = findByPortName(port); if (debug) qDebug() << "SymbianDeviceManager::releaseDevice" << port << idx << sender(); - if (idx != -1) { + if (idx != -1) d->m_devices[idx].releaseDevice(); - } else { + else qWarning("Attempt to release non-existing device %s.", qPrintable(port)); - } } void SymbianDeviceManager::setAdditionalInformation(const QString &port, const QString &ai) @@ -358,6 +497,8 @@ void SymbianDeviceManager::ensureInitialized() const void SymbianDeviceManager::update(bool emitSignals) { + QMutexLocker lock(&d->m_devicesLock); + static int n = 0; typedef SymbianDeviceList::iterator SymbianDeviceListIterator; @@ -377,6 +518,7 @@ void SymbianDeviceManager::update(bool emitSignals) } // Merge the lists and emit the respective added/removed signals, assuming // no one can plug a different device on the same port at the speed of lightning + SymbianDeviceList removedDevices; if (!d->m_devices.isEmpty()) { // Find deleted devices for (SymbianDeviceListIterator oldIt = d->m_devices.begin(); oldIt != d->m_devices.end(); ) { @@ -386,25 +528,33 @@ void SymbianDeviceManager::update(bool emitSignals) SymbianDevice toBeDeleted = *oldIt; toBeDeleted.forcedClose(); oldIt = d->m_devices.erase(oldIt); - if (emitSignals) - emit deviceRemoved(toBeDeleted); + removedDevices.append(toBeDeleted); } } } + SymbianDeviceList addedDevices; if (!newDevices.isEmpty()) { // Find new devices and insert in order foreach(const SymbianDevice &newDevice, newDevices) { if (!d->m_devices.contains(newDevice)) { d->m_devices.append(newDevice); - if (emitSignals) - emit deviceAdded(newDevice); + addedDevices.append(newDevice); } } if (d->m_devices.size() > 1) qStableSort(d->m_devices.begin(), d->m_devices.end()); } - if (emitSignals) + + lock.unlock(); + if (emitSignals) { + foreach (const SymbianDevice &device, removedDevices) { + emit deviceRemoved(device); + } + foreach (const SymbianDevice &device, addedDevices) { + emit deviceAdded(device); + } emit updated(); + } if (debug) qDebug("<SerialDeviceLister::update\n%s\n", qPrintable(toString())); @@ -457,7 +607,9 @@ SymbianDeviceManager::SymbianDeviceList SymbianDeviceManager::blueToothDevices() } // New kernel versions support /dev/ttyUSB0, /dev/ttyUSB1. Trk responds // on the latter (usually), try first. - static const char *usbTtyDevices[] = { "/dev/ttyUSB1", "/dev/ttyUSB0" }; + static const char *usbTtyDevices[] = { + "/dev/ttyUSB3", "/dev/ttyUSB2", "/dev/ttyUSB1", "/dev/ttyUSB0", + "/dev/ttyACM3", "/dev/ttyACM2", "/dev/ttyACM1", "/dev/ttyACM0"}; const int usbTtyCount = sizeof(usbTtyDevices)/sizeof(const char *); for (int d = 0; d < usbTtyCount; d++) { const QString ttyUSBDevice = QLatin1String(usbTtyDevices[d]); @@ -486,4 +638,106 @@ SYMBIANUTILS_EXPORT QDebug operator<<(QDebug d, const SymbianDeviceManager &sdm) return d; } -} // namespace SymbianUtilsInternal +OstChannel *SymbianDeviceManager::getOstChannel(const QString &port, uchar channelId) +{ + CodaDevicePtr coda = getCodaDevice(port); + if (coda.isNull() || !coda->device()->isOpen()) + return 0; + return new OstChannel(coda, channelId); +} + +struct OstChannelPrivate +{ + CodaDevicePtr m_codaPtr; + QByteArray m_dataBuffer; + uchar m_channelId; + bool m_hasReceivedData; +}; + +OstChannel::OstChannel(const CodaDevicePtr &codaPtr, uchar channelId) + : d(new OstChannelPrivate) +{ + d->m_codaPtr = codaPtr; + d->m_channelId = channelId; + d->m_hasReceivedData = false; + connect(codaPtr.data(), SIGNAL(unknownEvent(uchar, QByteArray)), this, SLOT(ostDataReceived(uchar,QByteArray))); + connect(codaPtr->device().data(), SIGNAL(aboutToClose()), this, SLOT(deviceAboutToClose())); + QIODevice::open(ReadWrite|Unbuffered); +} + +void OstChannel::close() +{ + QIODevice::close(); + if (d && d->m_codaPtr.data()) { + disconnect(d->m_codaPtr.data(), 0, this, 0); + SymbianDeviceManager::instance()->releaseCodaDevice(d->m_codaPtr); + } +} + +OstChannel::~OstChannel() +{ + close(); + delete d; +} + +void OstChannel::flush() +{ + //TODO d->m_codaPtr->device()- +} + +qint64 OstChannel::bytesAvailable() const +{ + return d->m_dataBuffer.size(); +} + +bool OstChannel::isSequential() const +{ + return true; +} + +qint64 OstChannel::readData(char *data, qint64 maxSize) +{ + qint64 amount = qMin(maxSize, (qint64)d->m_dataBuffer.size()); + qMemCopy(data, d->m_dataBuffer.constData(), amount); + d->m_dataBuffer.remove(0, amount); + return amount; +} + +qint64 OstChannel::writeData(const char *data, qint64 maxSize) +{ + static const qint64 KMaxOstPayload = 1024; + // If necessary, split the packet up + while (maxSize) { + QByteArray dataBuf = QByteArray::fromRawData(data, qMin(KMaxOstPayload, maxSize)); + d->m_codaPtr->writeCustomData(d->m_channelId, dataBuf); + data += dataBuf.length(); + maxSize -= dataBuf.length(); + } + return maxSize; +} + +void OstChannel::ostDataReceived(uchar channelId, const QByteArray &aData) +{ + if (channelId == d->m_channelId) { + d->m_hasReceivedData = true; + d->m_dataBuffer.append(aData); + emit readyRead(); + } +} + +Coda::CodaDevice& OstChannel::codaDevice() const +{ + return *d->m_codaPtr; +} + +bool OstChannel::hasReceivedData() const +{ + return isOpen() && d->m_hasReceivedData; +} + +void OstChannel::deviceAboutToClose() +{ + close(); +} + +} // namespace SymbianUtils |