Source code
Revision control
Copy as Markdown
// Copyright (C) 2017 Ford Motor Company.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant reason:default
#include "passthrucanbackend.h"
#include "passthrucanio.h"
#include <QEventLoop>
#include <QSettings>
namespace {
#ifdef Q_OS_WIN32
static inline QString registryPath()
{
return QStringLiteral("HKEY_LOCAL_MACHINE\\SOFTWARE\\PassThruSupport.04.04");
}
static QString canAdapterName(const QSettings &entries)
{
const int supportsCan = entries.value(QStringLiteral("CAN")).toInt();
if (supportsCan)
return entries.value(QStringLiteral("Name")).toString();
return {};
}
static QString libraryForAdapter(const QString &adapterName)
{
QString library;
QSettings entries (registryPath(), QSettings::NativeFormat);
const QStringList groups = entries.childGroups();
for (const auto &group : groups) {
entries.beginGroup(group);
const QString name = canAdapterName(entries);
if (!name.isEmpty() && (adapterName.isEmpty() ||
name.compare(adapterName, Qt::CaseInsensitive) == 0))
library = entries.value(QStringLiteral("FunctionLibrary")).toString();
entries.endGroup();
if (!library.isEmpty())
break;
}
return library;
}
#else // !Q_OS_WIN32
static QString libraryForAdapter(const QString &adapterName)
{
// Insert system-specific device name to J2534 library name mapping here.
// For now, allow the path to the J2534 library to be specified directly
// as the adapter name.
return adapterName;
}
#endif // !Q_OS_WIN32
} // anonymous namespace
PassThruCanBackend::PassThruCanBackend(const QString &name, QObject *parent)
: QCanBusDevice(parent)
, m_deviceName (name)
, m_canIO (new PassThruCanIO())
{
m_canIO->moveToThread(&m_ioThread);
// Signals emitted by the I/O thread, to be queued.
connect(m_canIO, &PassThruCanIO::errorOccurred,
this, &PassThruCanBackend::setError);
connect(m_canIO, &PassThruCanIO::openFinished,
this, &PassThruCanBackend::ackOpenFinished);
connect(m_canIO, &PassThruCanIO::closeFinished,
this, &PassThruCanBackend::ackCloseFinished);
connect(m_canIO, &PassThruCanIO::messagesReceived,
this, &PassThruCanBackend::enqueueReceivedFrames);
connect(m_canIO, &PassThruCanIO::messagesSent,
this, &QCanBusDevice::framesWritten);
}
PassThruCanBackend::~PassThruCanBackend()
{
if (state() != UnconnectedState) {
// If the I/O thread is still active at this point, we will have to
// wait for it to finish.
QEventLoop loop;
connect(&m_ioThread, &QThread::finished, &loop, &QEventLoop::quit);
if (state() != ClosingState)
disconnectDevice();
while (!m_ioThread.isFinished())
loop.exec(QEventLoop::ExcludeUserInputEvents);
}
m_canIO->deleteLater();
}
void PassThruCanBackend::setConfigurationParameter(ConfigurationKey key, const QVariant &value)
{
QCanBusDevice::setConfigurationParameter(key, value);
if (state() == ConnectedState)
applyConfig(key, value);
}
bool PassThruCanBackend::writeFrame(const QCanBusFrame &frame)
{
if (state() != ConnectedState) {
setError(tr("Device is not connected"), WriteError);
return false;
}
if (!frame.isValid()) {
setError(tr("Invalid CAN bus frame"), WriteError);
return false;
}
if (frame.frameType() != QCanBusFrame::DataFrame) {
setError(tr("Unsupported CAN frame type"), WriteError);
return false;
}
// Push the frame directly to the write queue of the worker thread,
// bypassing the QCanBusDevice output queue. Despite the duplicated
// queue, things are cleaner this way as it avoids a reverse dependency
// from the worker object on the QCanBusDevice object.
return m_canIO->enqueueMessage(frame);
}
QString PassThruCanBackend::interpretErrorFrame(const QCanBusFrame &)
{
// J2534 Pass-thru v04.04 does not seem to support error frames.
return {};
}
QList<QCanBusDeviceInfo> PassThruCanBackend::interfaces()
{
QList<QCanBusDeviceInfo> list;
#ifdef Q_OS_WIN32
QSettings entries (registryPath(), QSettings::NativeFormat);
const QStringList groups = entries.childGroups();
for (const auto &group : groups) {
entries.beginGroup(group);
const QString name = canAdapterName(entries);
if (!name.isEmpty())
list.append(createDeviceInfo(QStringLiteral("passthrucan"), name, false, false));
entries.endGroup();
}
#endif
return list;
}
QCanBusDeviceInfo PassThruCanBackend::deviceInfo() const
{
return createDeviceInfo(QStringLiteral("passthrucan"), m_deviceName, false, false);
}
bool PassThruCanBackend::open()
{
if (Q_UNLIKELY(state() != ConnectingState)) {
qCCritical(QT_CANBUS_PLUGINS_PASSTHRU, "Unexpected state on open");
return false;
}
// Support a special "adapter%subdevice" syntax to allow control of the
// device name passed to the J2534 library's PassThruOpen() function.
// If the "%subdevice" suffix is not used, the J2534 interface library
// will choose a default or ask the user.
const int splitPos = m_deviceName.indexOf(QChar::fromLatin1('%'));
const QString adapter = m_deviceName.left(splitPos);
QByteArray subDev;
if (splitPos >= 0)
subDev = QStringView{m_deviceName}.mid(splitPos + 1).toLatin1();
const QString library = libraryForAdapter(adapter);
if (library.isEmpty()) {
setError(tr("Adapter not found: %1").arg(adapter), ConnectionError);
return false;
}
bool ok = false;
uint bitRate = configurationParameter(BitRateKey).toUInt(&ok);
if (!ok) {
bitRate = 500*1000; // default initial bit rate
setConfigurationParameter(BitRateKey, bitRate);
}
m_ioThread.start();
return QMetaObject::invokeMethod(m_canIO, [this, library, subDev, bitRate] {
m_canIO->open(library, subDev, bitRate);
}, Qt::QueuedConnection);
}
void PassThruCanBackend::close()
{
if (Q_UNLIKELY(state() != ClosingState)) {
qCCritical(QT_CANBUS_PLUGINS_PASSTHRU, "Unexpected state on close");
return;
}
QMetaObject::invokeMethod(m_canIO, &PassThruCanIO::close, Qt::QueuedConnection);
}
void PassThruCanBackend::ackOpenFinished(bool success)
{
// Do not transition to connected state if close() has been called
// in the meantime.
if (state() != ConnectingState)
return;
if (success) {
const QVariant loopback = configurationParameter(LoopbackKey);
if (loopback.toBool())
applyConfig(LoopbackKey, loopback);
QVariant filters = configurationParameter(RawFilterKey);
if (!filters.isValid()) {
// Configure default match-all filter.
filters = QVariant::fromValue(QList<Filter>{Filter{}});
setConfigurationParameter(RawFilterKey, filters);
}
applyConfig(RawFilterKey, filters);
QMetaObject::invokeMethod(m_canIO, &PassThruCanIO::listen, Qt::QueuedConnection);
setState(ConnectedState);
} else {
setState(UnconnectedState);
}
}
void PassThruCanBackend::ackCloseFinished()
{
m_ioThread.exit(0);
m_ioThread.wait();
setState(UnconnectedState);
}
void PassThruCanBackend::applyConfig(QCanBusDevice::ConfigurationKey key, const QVariant &value)
{
QMetaObject::invokeMethod(m_canIO,
[this, key, value] { m_canIO->applyConfig(key, value); },
Qt::QueuedConnection);
}