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 "passthrucanio.h"
5
6#include <QLoggingCategory>
7#include <QtEndian>
8
9#include <cstring>
10
11PassThruCanIO::PassThruCanIO(QObject *parent)
12 : QObject(parent)
13 , m_ioBuffer (8, J2534::Message(J2534::Protocol::CAN))
14{
15}
16
17PassThruCanIO::~PassThruCanIO()
18{
19}
20
21void PassThruCanIO::open(const QString &library, const QByteArray &subDev, uint bitRate)
22{
23 if (Q_UNLIKELY(m_passThru)) {
24 qCCritical(QT_CANBUS_PLUGINS_PASSTHRU, "Pass-thru interface already open");
25 emit openFinished(success: false);
26 return;
27 }
28 qCDebug(QT_CANBUS_PLUGINS_PASSTHRU, "Loading interface library: %ls",
29 qUtf16Printable(library));
30
31 m_passThru = new J2534::PassThru(library, this);
32 J2534::PassThru::Status openStatus = m_passThru->lastError();
33
34 if (openStatus == J2534::PassThru::NoError)
35 openStatus = m_passThru->open(name: subDev, deviceId: &m_deviceId);
36
37 if (openStatus == J2534::PassThru::NoError
38 && m_passThru->connect(deviceId: m_deviceId, protocolId: J2534::Protocol::CAN,
39 flags: J2534::PassThru::CAN29BitID | J2534::PassThru::CANIDBoth,
40 baudRate: bitRate, channelId: &m_channelId) == J2534::PassThru::NoError) {
41 emit openFinished(success: true);
42 return;
43 }
44 emit errorOccurred(description: m_passThru->lastErrorString(),
45 error: QCanBusDevice::ConnectionError);
46
47 if (openStatus == J2534::PassThru::NoError
48 && m_passThru->close(deviceId: m_deviceId) != J2534::PassThru::NoError)
49 qCWarning(QT_CANBUS_PLUGINS_PASSTHRU, "Failed to close pass-thru device");
50
51 delete m_passThru;
52 m_passThru = nullptr;
53
54 emit openFinished(success: false);
55}
56
57void PassThruCanIO::close()
58{
59 if (Q_LIKELY(m_passThru)) {
60 delete m_idleNotifier;
61 m_idleNotifier = nullptr;
62
63 if (m_passThru->disconnect(channelId: m_channelId) != J2534::PassThru::NoError
64 || m_passThru->close(deviceId: m_deviceId) != J2534::PassThru::NoError) {
65
66 qCWarning(QT_CANBUS_PLUGINS_PASSTHRU, "Failed to close pass-thru device");
67 emit errorOccurred(description: m_passThru->lastErrorString(),
68 error: QCanBusDevice::ConnectionError);
69 }
70 delete m_passThru;
71 m_passThru = nullptr;
72 }
73 emit closeFinished();
74}
75
76void PassThruCanIO::applyConfig(QCanBusDevice::ConfigurationKey key, const QVariant &value)
77{
78 if (Q_UNLIKELY(!m_passThru)) {
79 qCCritical(QT_CANBUS_PLUGINS_PASSTHRU, "Pass-thru interface not open");
80 return;
81 }
82 bool success = true;
83
84 switch (key) {
85 case QCanBusDevice::RawFilterKey:
86 success = setMessageFilters(qvariant_cast<QList<QCanBusDevice::Filter>>(v: value));
87 break;
88 case QCanBusDevice::LoopbackKey:
89 success = setConfigValue(param: J2534::Config::Loopback, value: value.toBool());
90 break;
91 case QCanBusDevice::BitRateKey:
92 success = setConfigValue(param: J2534::Config::DataRate, value: value.toUInt());
93 break;
94 default:
95 emit errorOccurred(description: tr(s: "Unsupported configuration key: %1").arg(a: key),
96 error: QCanBusDevice::ConfigurationError);
97 break;
98 }
99 if (!success) {
100 emit errorOccurred(description: tr(s: "Configuration failed: %1").arg(a: m_passThru->lastErrorString()),
101 error: QCanBusDevice::ConfigurationError);
102 }
103}
104
105void PassThruCanIO::listen()
106{
107 if (Q_UNLIKELY(!m_passThru)) {
108 qCCritical(QT_CANBUS_PLUGINS_PASSTHRU, "Pass-thru interface not open");
109 return;
110 }
111 if (Q_UNLIKELY(m_idleNotifier)) {
112 qCCritical(QT_CANBUS_PLUGINS_PASSTHRU, "Idle notifier already created");
113 return;
114 }
115 m_idleNotifier = new QTimer(this);
116 connect(sender: m_idleNotifier, signal: &QTimer::timeout, context: this, slot: &PassThruCanIO::pollForMessages);
117
118 m_idleNotifier->start(msec: 0);
119}
120
121bool PassThruCanIO::enqueueMessage(const QCanBusFrame &frame)
122{
123 const QMutexLocker lock (&m_writeGuard);
124 m_writeQueue.append(t: frame);
125 return true;
126}
127
128bool PassThruCanIO::setMessageFilters(const QList<QCanBusDevice::Filter> &filters)
129{
130 if (m_passThru->clear(channelId: m_channelId, target: J2534::PassThru::MsgFilters) != J2534::PassThru::NoError)
131 return false;
132
133 J2534::Message pattern {J2534::Protocol::CAN};
134 pattern.setSize(4);
135 J2534::Message mask {J2534::Protocol::CAN};
136 mask.setSize(4);
137
138 for (const auto &filter : filters) {
139 if (filter.type != QCanBusFrame::DataFrame
140 && filter.type != QCanBusFrame::InvalidFrame) {
141 emit errorOccurred(description: tr(s: "Configuration failed: unsupported filter type"),
142 error: QCanBusDevice::ConfigurationError);
143 break;
144 }
145 if (filter.format & QCanBusDevice::Filter::MatchExtendedFormat)
146 pattern.setRxStatus(J2534::Message::InCAN29BitID);
147 else
148 pattern.setRxStatus({});
149
150 if (filter.format != QCanBusDevice::Filter::MatchBaseAndExtendedFormat)
151 mask.setRxStatus(J2534::Message::InCAN29BitID);
152 else
153 mask.setRxStatus({});
154
155 qToBigEndian<QCanBusFrame::FrameId>(src: filter.frameId & filter.frameIdMask, dest: pattern.data());
156 qToBigEndian<QCanBusFrame::FrameId>(src: filter.frameIdMask, dest: mask.data());
157
158 if (m_passThru->startMsgFilter(channelId: m_channelId, filterType: J2534::PassThru::PassFilter,
159 maskMsg: mask, patternMsg: pattern) != J2534::PassThru::NoError)
160 return false;
161 }
162 return true;
163}
164
165bool PassThruCanIO::setConfigValue(J2534::Config::Parameter param, ulong value)
166{
167 const J2534::Config config {param, value};
168
169 return (m_passThru->setConfig(channelId: m_channelId, params: &config) == J2534::PassThru::NoError);
170}
171
172void PassThruCanIO::pollForMessages()
173{
174 if (Q_UNLIKELY(!m_passThru)) {
175 qCCritical(QT_CANBUS_PLUGINS_PASSTHRU, "Pass-thru interface not open");
176 return;
177 }
178 const bool writePending = writeMessages();
179 readMessages(writePending);
180}
181
182bool PassThruCanIO::writeMessages()
183{
184 ulong numMsgs = m_ioBuffer.size();
185 {
186 const QMutexLocker lock (&m_writeGuard);
187 numMsgs = qMin<ulong>(a: m_writeQueue.size(), b: numMsgs);
188
189 for (ulong i = 0; i < numMsgs; ++i) {
190 const QCanBusFrame &frame = m_writeQueue.at(i);
191 J2534::Message &msg = m_ioBuffer[i];
192
193 const QByteArray payload = frame.payload();
194 const ulong payloadSize = qMin<ulong>(a: payload.size(),
195 b: J2534::Message::maxSize - 4);
196 msg.setRxStatus({});
197 msg.setTimestamp(0);
198 msg.setSize(4 + payloadSize);
199 msg.setExtraDataIndex(0);
200
201 if (frame.hasExtendedFrameFormat())
202 msg.setTxFlags(J2534::Message::OutCAN29BitID);
203 else
204 msg.setTxFlags({});
205
206 qToBigEndian<QCanBusFrame::FrameId>(src: frame.frameId(), dest: msg.data());
207 std::memcpy(dest: msg.data() + 4, src: payload.data(), n: payloadSize);
208 }
209 }
210 if (numMsgs == 0)
211 return false;
212
213 const auto status = m_passThru->writeMsgs(channelId: m_channelId, msgs: m_ioBuffer.constData(),
214 numMsgs: &numMsgs, timeout: pollTimeout);
215 if (status == J2534::PassThru::BufferFull)
216 return false;
217
218 if (status != J2534::PassThru::NoError && status != J2534::PassThru::Timeout) {
219 emit errorOccurred(description: tr(s: "Message write failed: %1").arg(a: m_passThru->lastErrorString()),
220 error: QCanBusDevice::WriteError);
221 return false;
222 }
223 if (numMsgs == 0)
224 return false;
225
226 bool morePending;
227 {
228 const QMutexLocker lock (&m_writeGuard);
229 // De-queue successfully written frames.
230 m_writeQueue.erase(abegin: m_writeQueue.begin(), aend: m_writeQueue.begin() + numMsgs);
231 morePending = !m_writeQueue.isEmpty();
232 }
233 emit messagesSent(count: numMsgs);
234
235 return morePending;
236}
237
238void PassThruCanIO::readMessages(bool writePending)
239{
240 // If there are outgoing messages waiting to be written, just check
241 // for already received messages but do not block waiting for more.
242 const uint timeout = (writePending) ? 0 : pollTimeout;
243
244 ulong numMsgs = m_ioBuffer.size();
245 const auto status = m_passThru->readMsgs(channelId: m_channelId, msgs: m_ioBuffer.data(),
246 numMsgs: &numMsgs, timeout);
247 if (status == J2534::PassThru::BufferEmpty)
248 return;
249
250 if (status != J2534::PassThru::NoError && status != J2534::PassThru::Timeout) {
251 emit errorOccurred(description: tr(s: "Message read failed: %1").arg(a: m_passThru->lastErrorString()),
252 error: QCanBusDevice::ReadError);
253 if (status != J2534::PassThru::BufferOverflow)
254 return;
255 }
256 const int numFrames = qMin<ulong>(a: m_ioBuffer.size(), b: numMsgs);
257 QList<QCanBusFrame> frames;
258 frames.reserve(asize: numFrames);
259
260 for (int i = 0; i < numFrames; ++i) {
261 const J2534::Message &msg = m_ioBuffer.at(i);
262 if (Q_UNLIKELY(msg.size() < 4)
263 || Q_UNLIKELY(msg.size() > J2534::Message::maxSize)) {
264 // This normally shouldn't happen, so a log message is appropriate.
265 qCWarning(QT_CANBUS_PLUGINS_PASSTHRU,
266 "Message with invalid size %lu received", msg.size());
267 continue;
268 }
269 const QCanBusFrame::FrameId msgId = qFromBigEndian<QCanBusFrame::FrameId>(src: msg.data());
270 const QByteArray payload (msg.data() + 4, msg.size() - 4);
271
272 QCanBusFrame frame (msgId, payload);
273 frame.setExtendedFrameFormat((msg.rxStatus() & J2534::Message::InCAN29BitID) != 0);
274 frame.setLocalEcho((msg.rxStatus() & J2534::Message::InTxMsgType) != 0);
275 frame.setTimeStamp(QCanBusFrame::TimeStamp::fromMicroSeconds(usec: msg.timestamp()));
276
277 frames.append(t: std::move(frame));
278 }
279 if (!frames.isEmpty())
280 emit messagesReceived(frames: std::move(frames));
281}
282

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