| 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 |  |