1// Copyright (C) 2017 Denis Shienkov <denis.shienkov@gmail.com>
2// Copyright (C) 2017 The Qt Company Ltd.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4
5#include "tinycanbackend.h"
6#include "tinycanbackend_p.h"
7
8#include "tinycan_symbols_p.h"
9
10#include <QtSerialBus/qcanbusdevice.h>
11
12#include <QtCore/qtimer.h>
13#include <QtCore/qmutex.h>
14#include <QtCore/qcoreevent.h>
15#include <QtCore/qloggingcategory.h>
16
17#include <algorithm>
18
19QT_BEGIN_NAMESPACE
20
21Q_DECLARE_LOGGING_CATEGORY(QT_CANBUS_PLUGINS_TINYCAN)
22
23#ifndef LINK_LIBMHSTCAN
24Q_GLOBAL_STATIC(QLibrary, mhstcanLibrary)
25#endif
26
27bool TinyCanBackend::canCreate(QString *errorReason)
28{
29#ifdef LINK_LIBMHSTCAN
30 return true;
31#else
32 static bool symbolsResolved = resolveTinyCanSymbols(mhstcanLibrary: mhstcanLibrary());
33 if (Q_UNLIKELY(!symbolsResolved)) {
34 *errorReason = mhstcanLibrary()->errorString();
35 return false;
36 }
37 return true;
38#endif
39}
40
41QList<QCanBusDeviceInfo> TinyCanBackend::interfaces()
42{
43 QList<QCanBusDeviceInfo> result;
44 result.append(t: createDeviceInfo(QStringLiteral("tinycan"), QStringLiteral("can0.0"),
45 isVirtual: false, isFlexibleDataRateCapable: false));
46 return result;
47}
48
49namespace {
50
51struct TinyCanGlobal {
52 QList<TinyCanBackendPrivate *> channels;
53 QMutex mutex;
54};
55
56} // namespace
57
58Q_GLOBAL_STATIC(TinyCanGlobal, gTinyCan)
59
60class TinyCanWriteNotifier : public QTimer
61{
62 // no Q_OBJECT macro!
63public:
64 TinyCanWriteNotifier(TinyCanBackendPrivate *d, QObject *parent)
65 : QTimer(parent)
66 , dptr(d)
67 {
68 }
69
70protected:
71 void timerEvent(QTimerEvent *e) override
72 {
73 if (e->timerId() == timerId()) {
74 dptr->startWrite();
75 return;
76 }
77 QTimer::timerEvent(e);
78 }
79
80private:
81 TinyCanBackendPrivate * const dptr;
82};
83
84static int driverRefCount = 0;
85
86static void DRV_CALLBACK_TYPE canRxEventCallback(quint32 index, TCanMsg *frame, qint32 count)
87{
88 Q_UNUSED(frame);
89 Q_UNUSED(count);
90
91 QMutexLocker lock(&gTinyCan->mutex);
92 for (TinyCanBackendPrivate *p : std::as_const(t&: gTinyCan->channels)) {
93 if (p->channelIndex == int(index)) {
94 p->startRead();
95 return;
96 }
97 }
98}
99
100TinyCanBackendPrivate::TinyCanBackendPrivate(TinyCanBackend *q)
101 : q_ptr(q)
102{
103 startupDriver();
104
105 QMutexLocker lock(&gTinyCan->mutex);
106 gTinyCan->channels.append(t: this);
107}
108
109TinyCanBackendPrivate::~TinyCanBackendPrivate()
110{
111 cleanupDriver();
112
113 QMutexLocker lock(&gTinyCan->mutex);
114 gTinyCan->channels.removeAll(t: this);
115}
116
117struct BitrateItem
118{
119 int bitrate;
120 int code;
121};
122
123struct BitrateLessFunctor
124{
125 bool operator()( const BitrateItem &item1, const BitrateItem &item2) const
126 {
127 return item1.bitrate < item2.bitrate;
128 }
129};
130
131static int bitrateCodeFromBitrate(int bitrate)
132{
133 static const BitrateItem bitratetable[] = {
134 { .bitrate: 10000, CAN_10K_BIT },
135 { .bitrate: 20000, CAN_20K_BIT },
136 { .bitrate: 50000, CAN_50K_BIT },
137 { .bitrate: 100000, CAN_100K_BIT },
138 { .bitrate: 125000, CAN_125K_BIT },
139 { .bitrate: 250000, CAN_250K_BIT },
140 { .bitrate: 500000, CAN_500K_BIT },
141 { .bitrate: 800000, CAN_800K_BIT },
142 { .bitrate: 1000000, CAN_1M_BIT }
143 };
144
145 static const BitrateItem *endtable = bitratetable + (sizeof(bitratetable) / sizeof(*bitratetable));
146
147 const BitrateItem item = { .bitrate: bitrate , .code: 0 };
148 const BitrateItem *where = std::lower_bound(first: bitratetable, last: endtable, val: item, comp: BitrateLessFunctor());
149 return where != endtable ? where->code : -1;
150}
151
152bool TinyCanBackendPrivate::open()
153{
154 Q_Q(TinyCanBackend);
155
156 {
157 char options[] = "AutoConnect=1;AutoReopen=0";
158 const int ret = ::CanSetOptions(options);
159 if (Q_UNLIKELY(ret < 0)) {
160 q->setError(errorText: systemErrorString(errorCode: ret), QCanBusDevice::CanBusError::ConnectionError);
161 return false;
162 }
163 }
164
165 {
166 const int ret = ::CanDeviceOpen(channelIndex, nullptr);
167 if (Q_UNLIKELY(ret < 0)) {
168 q->setError(errorText: systemErrorString(errorCode: ret), QCanBusDevice::CanBusError::ConnectionError);
169 return false;
170 }
171 }
172
173 {
174 const int ret = ::CanSetMode(channelIndex, OP_CAN_START, CAN_CMD_ALL_CLEAR);
175 if (Q_UNLIKELY(ret < 0)) {
176 q->setError(errorText: systemErrorString(errorCode: ret), QCanBusDevice::CanBusError::ConnectionError);
177 ::CanDeviceClose(channelIndex);
178 return false;
179 }
180 }
181
182 writeNotifier = new TinyCanWriteNotifier(this, q);
183 writeNotifier->setInterval(0);
184
185 isOpen = true;
186 return true;
187}
188
189void TinyCanBackendPrivate::close()
190{
191 Q_Q(TinyCanBackend);
192
193 delete writeNotifier;
194 writeNotifier = nullptr;
195
196 const int ret = ::CanDeviceClose(channelIndex);
197 if (Q_UNLIKELY(ret < 0))
198 q->setError(errorText: systemErrorString(errorCode: ret), QCanBusDevice::CanBusError::ConnectionError);
199
200 isOpen = false;
201}
202
203bool TinyCanBackendPrivate::setConfigurationParameter(QCanBusDevice::ConfigurationKey key,
204 const QVariant &value)
205{
206 Q_Q(TinyCanBackend);
207
208 switch (key) {
209 case QCanBusDevice::BitRateKey:
210 return setBitRate(value.toInt());
211 default:
212 q->setError(errorText: TinyCanBackend::tr(s: "Unsupported configuration key: %1").arg(a: key),
213 QCanBusDevice::ConfigurationError);
214 return false;
215 }
216}
217
218// These error codes taked from the errors.h file, which
219// exists only in linux sources.
220QString TinyCanBackendPrivate::systemErrorString(int errorCode)
221{
222 switch (errorCode) {
223 case 0:
224 return TinyCanBackend::tr(s: "No error");
225 case -1:
226 return TinyCanBackend::tr(s: "Driver not initialized");
227 case -2:
228 return TinyCanBackend::tr(s: "Invalid parameters values were passed");
229 case -3:
230 return TinyCanBackend::tr(s: "Invalid index value");
231 case -4:
232 return TinyCanBackend::tr(s: "More invalid CAN-channel");
233 case -5:
234 return TinyCanBackend::tr(s: "General error");
235 case -6:
236 return TinyCanBackend::tr(s: "The FIFO cannot be written");
237 case -7:
238 return TinyCanBackend::tr(s: "The buffer cannot be written");
239 case -8:
240 return TinyCanBackend::tr(s: "The FIFO cannot be read");
241 case -9:
242 return TinyCanBackend::tr(s: "The buffer cannot be read");
243 case -10:
244 return TinyCanBackend::tr(s: "Variable not found");
245 case -11:
246 return TinyCanBackend::tr(s: "Reading of the variable does not permit");
247 case -12:
248 return TinyCanBackend::tr(s: "Reading buffer for variable too small");
249 case -13:
250 return TinyCanBackend::tr(s: "Writing of the variable does not permit");
251 case -14:
252 return TinyCanBackend::tr(s: "The string/stream to be written is to majority");
253 case -15:
254 return TinyCanBackend::tr(s: "Fell short min of value");
255 case -16:
256 return TinyCanBackend::tr(s: "Max value crossed");
257 case -17:
258 return TinyCanBackend::tr(s: "Access refuses");
259 case -18:
260 return TinyCanBackend::tr(s: "Invalid value of CAN speed");
261 case -19:
262 return TinyCanBackend::tr(s: "Invalid value of baud rate");
263 case -20:
264 return TinyCanBackend::tr(s: "Value not put");
265 case -21:
266 return TinyCanBackend::tr(s: "No connection to the hardware");
267 case -22:
268 return TinyCanBackend::tr(s: "Communication error to the hardware");
269 case -23:
270 return TinyCanBackend::tr(s: "Hardware sends wrong number of parameters");
271 case -24:
272 return TinyCanBackend::tr(s: "Not enough main memory");
273 case -25:
274 return TinyCanBackend::tr(s: "The system cannot provide the enough resources");
275 case -26:
276 return TinyCanBackend::tr(s: "A system call returns with an error");
277 case -27:
278 return TinyCanBackend::tr(s: "The main thread is occupied");
279 case -28:
280 return TinyCanBackend::tr(s: "User allocated memory not found");
281 case -29:
282 return TinyCanBackend::tr(s: "The main thread cannot be launched");
283 // the codes -30...-33 are skipped, as they belongs to sockets
284 default:
285 return TinyCanBackend::tr(s: "Unknown error");
286 }
287}
288
289static int channelIndexFromName(const QString &interfaceName)
290{
291 if (interfaceName == QStringLiteral("can0.0"))
292 return INDEX_CAN_KANAL_A;
293 else if (interfaceName == QStringLiteral("can0.1"))
294 return INDEX_CAN_KANAL_B;
295 else
296 return INDEX_INVALID;
297}
298
299void TinyCanBackendPrivate::setupChannel(const QString &interfaceName)
300{
301 channelIndex = channelIndexFromName(interfaceName);
302}
303
304// Calls only in constructor
305void TinyCanBackendPrivate::setupDefaultConfigurations()
306{
307 Q_Q(TinyCanBackend);
308
309 q->setConfigurationParameter(key: QCanBusDevice::BitRateKey, value: 500000);
310}
311
312void TinyCanBackendPrivate::startWrite()
313{
314 Q_Q(TinyCanBackend);
315
316 if (!q->hasOutgoingFrames()) {
317 writeNotifier->stop();
318 return;
319 }
320
321 const QCanBusFrame frame = q->dequeueOutgoingFrame();
322 const QByteArray payload = frame.payload();
323 const qsizetype payloadSize = payload.size();
324
325 TCanMsg message = {};
326 message.Id = frame.frameId();
327 message.Flags.Flag.Len = payloadSize;
328 message.Flags.Flag.Error = (frame.frameType() == QCanBusFrame::ErrorFrame);
329 message.Flags.Flag.RTR = (frame.frameType() == QCanBusFrame::RemoteRequestFrame);
330 message.Flags.Flag.TxD = 1;
331 message.Flags.Flag.EFF = frame.hasExtendedFrameFormat();
332
333 const qint32 messagesToWrite = 1;
334 ::memcpy(dest: message.Data.Bytes, src: payload.constData(), n: payloadSize);
335 const int ret = ::CanTransmit(channelIndex, &message, messagesToWrite);
336 if (Q_UNLIKELY(ret < 0))
337 q->setError(errorText: systemErrorString(errorCode: ret), QCanBusDevice::CanBusError::WriteError);
338 else
339 emit q->framesWritten(framesCount: messagesToWrite);
340
341 if (q->hasOutgoingFrames() && !writeNotifier->isActive())
342 writeNotifier->start();
343}
344
345// this method is called from the different thread!
346void TinyCanBackendPrivate::startRead()
347{
348 Q_Q(TinyCanBackend);
349
350 QList<QCanBusFrame> newFrames;
351
352 for (;;) {
353 if (!::CanReceiveGetCount(channelIndex))
354 break;
355
356 TCanMsg message = {};
357
358 const int messagesToRead = 1;
359 const int ret = ::CanReceive(channelIndex, &message, messagesToRead);
360 if (Q_UNLIKELY(ret < 0)) {
361 q->setError(errorText: systemErrorString(errorCode: ret), QCanBusDevice::CanBusError::ReadError);
362
363 TDeviceStatus status = {};
364
365 if (::CanGetDeviceStatus(channelIndex, &status) < 0) {
366 q->setError(errorText: systemErrorString(errorCode: ret), QCanBusDevice::CanBusError::ReadError);
367 } else {
368 if (status.CanStatus == CAN_STATUS_BUS_OFF) {
369 qCWarning(QT_CANBUS_PLUGINS_TINYCAN, "CAN bus is in off state, trying to reset the bus.");
370 resetController();
371 }
372 }
373
374 continue;
375 }
376
377 QCanBusFrame frame(message.Id, QByteArray(reinterpret_cast<char *>(message.Data.Bytes),
378 int(message.Flags.Flag.Len)));
379 frame.setTimeStamp(QCanBusFrame::TimeStamp(message.Time.Sec, message.Time.USec));
380 frame.setExtendedFrameFormat(message.Flags.Flag.EFF);
381
382 if (message.Flags.Flag.Error)
383 frame.setFrameType(QCanBusFrame::ErrorFrame);
384 else if (message.Flags.Flag.RTR)
385 frame.setFrameType(QCanBusFrame::RemoteRequestFrame);
386 else
387 frame.setFrameType(QCanBusFrame::DataFrame);
388
389 newFrames.append(t: std::move(frame));
390 }
391
392 q->enqueueReceivedFrames(newFrames);
393}
394
395void TinyCanBackendPrivate::startupDriver()
396{
397 Q_Q(TinyCanBackend);
398
399 if (driverRefCount == 0) {
400 const int ret = ::CanInitDriver(nullptr);
401 if (Q_UNLIKELY(ret < 0)) {
402 q->setError(errorText: systemErrorString(errorCode: ret), QCanBusDevice::CanBusError::ConnectionError);
403 return;
404 }
405
406 ::CanSetRxEventCallback(&canRxEventCallback);
407 ::CanSetEvents(EVENT_ENABLE_RX_MESSAGES);
408
409 } else if (Q_UNLIKELY(driverRefCount < 0)) {
410 qCCritical(QT_CANBUS_PLUGINS_TINYCAN, "Wrong driver reference counter: %d",
411 driverRefCount);
412 return;
413 }
414
415 ++driverRefCount;
416}
417
418void TinyCanBackendPrivate::cleanupDriver()
419{
420 --driverRefCount;
421
422 if (Q_UNLIKELY(driverRefCount < 0)) {
423 qCCritical(QT_CANBUS_PLUGINS_TINYCAN, "Wrong driver reference counter: %d",
424 driverRefCount);
425 driverRefCount = 0;
426 } else if (driverRefCount == 0) {
427 ::CanSetEvents(EVENT_DISABLE_ALL);
428 ::CanDownDriver();
429 }
430}
431
432void TinyCanBackendPrivate::resetController()
433{
434 Q_Q(TinyCanBackend);
435 qint32 ret = ::CanSetMode(channelIndex, OP_CAN_RESET, CAN_CMD_NONE);
436 if (Q_UNLIKELY(ret < 0)) {
437 const QString errorString = systemErrorString(errorCode: ret);
438 qCWarning(QT_CANBUS_PLUGINS_TINYCAN, "Cannot perform hardware reset: %ls",
439 qUtf16Printable(errorString));
440 q->setError(errorText: errorString, QCanBusDevice::CanBusError::ConfigurationError);
441 }
442}
443
444bool TinyCanBackendPrivate::setBitRate(int bitrate)
445{
446 Q_Q(TinyCanBackend);
447
448 const int bitrateCode = bitrateCodeFromBitrate(bitrate);
449 if (Q_UNLIKELY(bitrateCode == -1)) {
450 q->setError(errorText: TinyCanBackend::tr(s: "Unsupported bitrate value"),
451 QCanBusDevice::ConfigurationError);
452 return false;
453 }
454
455 if (isOpen) {
456 const int ret = ::CanSetSpeed(channelIndex, bitrateCode);
457 if (Q_UNLIKELY(ret < 0)) {
458 q->setError(errorText: systemErrorString(errorCode: ret), QCanBusDevice::CanBusError::ConfigurationError);
459 return false;
460 }
461 }
462
463 return true;
464}
465
466TinyCanBackend::TinyCanBackend(const QString &name, QObject *parent)
467 : QCanBusDevice(parent)
468 , d_ptr(new TinyCanBackendPrivate(this))
469{
470 Q_D(TinyCanBackend);
471
472 d->setupChannel(name);
473 d->setupDefaultConfigurations();
474}
475
476TinyCanBackend::~TinyCanBackend()
477{
478 close();
479 delete d_ptr;
480}
481
482bool TinyCanBackend::open()
483{
484 Q_D(TinyCanBackend);
485
486 if (!d->isOpen) {
487 if (!d->open()) {
488 close(); // sets UnconnectedState
489 return false;
490 }
491
492 // apply all stored configurations
493 const auto keys = configurationKeys();
494 for (ConfigurationKey key : keys) {
495 const QVariant param = configurationParameter(key);
496 const bool success = d->setConfigurationParameter(key, value: param);
497 if (Q_UNLIKELY(!success)) {
498 qCWarning(QT_CANBUS_PLUGINS_TINYCAN, "Cannot apply parameter: %d with value: %ls.",
499 key, qUtf16Printable(param.toString()));
500 }
501 }
502 }
503
504 setState(QCanBusDevice::ConnectedState);
505 return true;
506}
507
508void TinyCanBackend::close()
509{
510 Q_D(TinyCanBackend);
511
512 d->close();
513
514 setState(QCanBusDevice::UnconnectedState);
515}
516
517void TinyCanBackend::setConfigurationParameter(ConfigurationKey key, const QVariant &value)
518{
519 Q_D(TinyCanBackend);
520
521 if (d->setConfigurationParameter(key, value))
522 QCanBusDevice::setConfigurationParameter(key, value);
523}
524
525bool TinyCanBackend::writeFrame(const QCanBusFrame &newData)
526{
527 Q_D(TinyCanBackend);
528
529 if (Q_UNLIKELY(state() != QCanBusDevice::ConnectedState))
530 return false;
531
532 if (Q_UNLIKELY(!newData.isValid())) {
533 setError(errorText: tr(s: "Cannot write invalid QCanBusFrame"), QCanBusDevice::WriteError);
534 return false;
535 }
536
537 if (Q_UNLIKELY(newData.frameType() != QCanBusFrame::DataFrame
538 && newData.frameType() != QCanBusFrame::RemoteRequestFrame
539 && newData.frameType() != QCanBusFrame::ErrorFrame)) {
540 setError(errorText: tr(s: "Unable to write a frame with unacceptable type"),
541 QCanBusDevice::WriteError);
542 return false;
543 }
544
545 // CAN FD frame format not supported at this stage
546 if (Q_UNLIKELY(newData.hasFlexibleDataRateFormat())) {
547 setError(errorText: tr(s: "CAN FD frame format not supported."), QCanBusDevice::WriteError);
548 return false;
549 }
550
551 enqueueOutgoingFrame(newFrame: newData);
552
553 if (!d->writeNotifier->isActive())
554 d->writeNotifier->start();
555
556 return true;
557}
558
559// TODO: Implement me
560QString TinyCanBackend::interpretErrorFrame(const QCanBusFrame &errorFrame)
561{
562 Q_UNUSED(errorFrame);
563
564 return QString();
565}
566
567void TinyCanBackend::resetController()
568{
569 Q_D(TinyCanBackend);
570 d->resetController();
571}
572
573QCanBusDeviceInfo TinyCanBackend::deviceInfo() const
574{
575 return createDeviceInfo(QStringLiteral("tinycan"), QStringLiteral("can0.0"), isVirtual: false, isFlexibleDataRateCapable: false);
576}
577
578QT_END_NAMESPACE
579

source code of qtserialbus/src/plugins/canbus/tinycan/tinycanbackend.cpp