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 | |
11 | PassThruCanIO::PassThruCanIO(QObject *parent) |
12 | : QObject(parent) |
13 | , m_ioBuffer (8, J2534::Message(J2534::Protocol::CAN)) |
14 | { |
15 | } |
16 | |
17 | PassThruCanIO::~PassThruCanIO() |
18 | { |
19 | } |
20 | |
21 | void 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 | |
57 | void 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 | |
76 | void 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 | |
105 | void 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 | |
121 | bool PassThruCanIO::enqueueMessage(const QCanBusFrame &frame) |
122 | { |
123 | const QMutexLocker lock (&m_writeGuard); |
124 | m_writeQueue.append(t: frame); |
125 | return true; |
126 | } |
127 | |
128 | bool 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 | |
165 | bool 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 | |
172 | void 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 | |
182 | bool 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 | |
238 | void 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 | |