1// Copyright (C) 2017 Ford Motor Company.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "passthrucanbackend.h"
5#include "passthrucanio.h"
6
7#include <QEventLoop>
8#include <QSettings>
9
10namespace {
11
12#ifdef Q_OS_WIN32
13
14static inline QString registryPath()
15{
16 return QStringLiteral("HKEY_LOCAL_MACHINE\\SOFTWARE\\PassThruSupport.04.04");
17}
18
19static QString canAdapterName(const QSettings &entries)
20{
21 const int supportsCan = entries.value(QStringLiteral("CAN")).toInt();
22 if (supportsCan)
23 return entries.value(QStringLiteral("Name")).toString();
24 return {};
25}
26
27static QString libraryForAdapter(const QString &adapterName)
28{
29 QString library;
30 QSettings entries (registryPath(), QSettings::NativeFormat);
31 const QStringList groups = entries.childGroups();
32
33 for (const auto &group : groups) {
34 entries.beginGroup(group);
35
36 const QString name = canAdapterName(entries);
37 if (!name.isEmpty() && (adapterName.isEmpty() ||
38 name.compare(adapterName, Qt::CaseInsensitive) == 0))
39 library = entries.value(QStringLiteral("FunctionLibrary")).toString();
40
41 entries.endGroup();
42
43 if (!library.isEmpty())
44 break;
45 }
46 return library;
47}
48
49#else // !Q_OS_WIN32
50
51static QString libraryForAdapter(const QString &adapterName)
52{
53 // Insert system-specific device name to J2534 library name mapping here.
54 // For now, allow the path to the J2534 library to be specified directly
55 // as the adapter name.
56 return adapterName;
57}
58
59#endif // !Q_OS_WIN32
60
61} // anonymous namespace
62
63PassThruCanBackend::PassThruCanBackend(const QString &name, QObject *parent)
64 : QCanBusDevice(parent)
65 , m_deviceName (name)
66 , m_canIO (new PassThruCanIO())
67{
68 m_canIO->moveToThread(thread: &m_ioThread);
69
70 // Signals emitted by the I/O thread, to be queued.
71 connect(sender: m_canIO, signal: &PassThruCanIO::errorOccurred,
72 context: this, slot: &PassThruCanBackend::setError);
73 connect(sender: m_canIO, signal: &PassThruCanIO::openFinished,
74 context: this, slot: &PassThruCanBackend::ackOpenFinished);
75 connect(sender: m_canIO, signal: &PassThruCanIO::closeFinished,
76 context: this, slot: &PassThruCanBackend::ackCloseFinished);
77 connect(sender: m_canIO, signal: &PassThruCanIO::messagesReceived,
78 context: this, slot: &PassThruCanBackend::enqueueReceivedFrames);
79 connect(sender: m_canIO, signal: &PassThruCanIO::messagesSent,
80 context: this, slot: &QCanBusDevice::framesWritten);
81}
82
83PassThruCanBackend::~PassThruCanBackend()
84{
85 if (state() != UnconnectedState) {
86 // If the I/O thread is still active at this point, we will have to
87 // wait for it to finish.
88 QEventLoop loop;
89 connect(sender: &m_ioThread, signal: &QThread::finished, context: &loop, slot: &QEventLoop::quit);
90
91 if (state() != ClosingState)
92 disconnectDevice();
93
94 while (!m_ioThread.isFinished())
95 loop.exec(flags: QEventLoop::ExcludeUserInputEvents);
96 }
97 m_canIO->deleteLater();
98}
99
100void PassThruCanBackend::setConfigurationParameter(ConfigurationKey key, const QVariant &value)
101{
102 QCanBusDevice::setConfigurationParameter(key, value);
103
104 if (state() == ConnectedState)
105 applyConfig(key, value);
106}
107
108bool PassThruCanBackend::writeFrame(const QCanBusFrame &frame)
109{
110 if (state() != ConnectedState) {
111 setError(errorText: tr(s: "Device is not connected"), WriteError);
112 return false;
113 }
114 if (!frame.isValid()) {
115 setError(errorText: tr(s: "Invalid CAN bus frame"), WriteError);
116 return false;
117 }
118 if (frame.frameType() != QCanBusFrame::DataFrame) {
119 setError(errorText: tr(s: "Unsupported CAN frame type"), WriteError);
120 return false;
121 }
122 // Push the frame directly to the write queue of the worker thread,
123 // bypassing the QCanBusDevice output queue. Despite the duplicated
124 // queue, things are cleaner this way as it avoids a reverse dependency
125 // from the worker object on the QCanBusDevice object.
126 return m_canIO->enqueueMessage(frame);
127}
128
129QString PassThruCanBackend::interpretErrorFrame(const QCanBusFrame &)
130{
131 // J2534 Pass-thru v04.04 does not seem to support error frames.
132 return {};
133}
134
135QList<QCanBusDeviceInfo> PassThruCanBackend::interfaces()
136{
137 QList<QCanBusDeviceInfo> list;
138#ifdef Q_OS_WIN32
139 QSettings entries (registryPath(), QSettings::NativeFormat);
140 const QStringList groups = entries.childGroups();
141
142 for (const auto &group : groups) {
143 entries.beginGroup(group);
144
145 const QString name = canAdapterName(entries);
146 if (!name.isEmpty())
147 list.append(createDeviceInfo(QStringLiteral("passthrucan"), name, false, false));
148 entries.endGroup();
149 }
150#endif
151 return list;
152}
153
154QCanBusDeviceInfo PassThruCanBackend::deviceInfo() const
155{
156 return createDeviceInfo(QStringLiteral("passthrucan"), name: m_deviceName, isVirtual: false, isFlexibleDataRateCapable: false);
157}
158
159bool PassThruCanBackend::open()
160{
161 if (Q_UNLIKELY(state() != ConnectingState)) {
162 qCCritical(QT_CANBUS_PLUGINS_PASSTHRU, "Unexpected state on open");
163 return false;
164 }
165 // Support a special "adapter%subdevice" syntax to allow control of the
166 // device name passed to the J2534 library's PassThruOpen() function.
167 // If the "%subdevice" suffix is not used, the J2534 interface library
168 // will choose a default or ask the user.
169 const int splitPos = m_deviceName.indexOf(c: QChar::fromLatin1(c: '%'));
170 const QString adapter = m_deviceName.left(n: splitPos);
171 QByteArray subDev;
172
173 if (splitPos >= 0)
174 subDev = QStringView{m_deviceName}.mid(pos: splitPos + 1).toLatin1();
175
176 const QString library = libraryForAdapter(adapterName: adapter);
177 if (library.isEmpty()) {
178 setError(errorText: tr(s: "Adapter not found: %1").arg(a: adapter), ConnectionError);
179 return false;
180 }
181 bool ok = false;
182 uint bitRate = configurationParameter(key: BitRateKey).toUInt(ok: &ok);
183 if (!ok) {
184 bitRate = 500*1000; // default initial bit rate
185 setConfigurationParameter(key: BitRateKey, value: bitRate);
186 }
187 m_ioThread.start();
188
189 return QMetaObject::invokeMethod(object: m_canIO, function: [this, library, subDev, bitRate] {
190 m_canIO->open(library, subDev, bitRate);
191 }, type: Qt::QueuedConnection);
192}
193
194void PassThruCanBackend::close()
195{
196 if (Q_UNLIKELY(state() != ClosingState)) {
197 qCCritical(QT_CANBUS_PLUGINS_PASSTHRU, "Unexpected state on close");
198 return;
199 }
200 QMetaObject::invokeMethod(object: m_canIO, function: &PassThruCanIO::close, type: Qt::QueuedConnection);
201}
202
203void PassThruCanBackend::ackOpenFinished(bool success)
204{
205 // Do not transition to connected state if close() has been called
206 // in the meantime.
207 if (state() != ConnectingState)
208 return;
209
210 if (success) {
211 const QVariant loopback = configurationParameter(key: LoopbackKey);
212 if (loopback.toBool())
213 applyConfig(key: LoopbackKey, value: loopback);
214
215 QVariant filters = configurationParameter(key: RawFilterKey);
216 if (!filters.isValid()) {
217 // Configure default match-all filter.
218 filters = QVariant::fromValue(value: QList<Filter>{Filter{}});
219 setConfigurationParameter(key: RawFilterKey, value: filters);
220 }
221 applyConfig(key: RawFilterKey, value: filters);
222
223 QMetaObject::invokeMethod(object: m_canIO, function: &PassThruCanIO::listen, type: Qt::QueuedConnection);
224
225 setState(ConnectedState);
226 } else {
227 setState(UnconnectedState);
228 }
229}
230
231void PassThruCanBackend::ackCloseFinished()
232{
233 m_ioThread.exit(retcode: 0);
234 m_ioThread.wait();
235
236 setState(UnconnectedState);
237}
238
239void PassThruCanBackend::applyConfig(QCanBusDevice::ConfigurationKey key, const QVariant &value)
240{
241 QMetaObject::invokeMethod(object: m_canIO,
242 function: [this, key, value] { m_canIO->applyConfig(key, value); },
243 type: Qt::QueuedConnection);
244}
245

source code of qtserialbus/src/plugins/canbus/passthrucan/passthrucanbackend.cpp