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 | |
19 | QT_BEGIN_NAMESPACE |
20 | |
21 | Q_DECLARE_LOGGING_CATEGORY(QT_CANBUS_PLUGINS_TINYCAN) |
22 | |
23 | #ifndef LINK_LIBMHSTCAN |
24 | Q_GLOBAL_STATIC(QLibrary, mhstcanLibrary) |
25 | #endif |
26 | |
27 | bool 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 | |
41 | QList<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 | |
49 | namespace { |
50 | |
51 | struct TinyCanGlobal { |
52 | QList<TinyCanBackendPrivate *> channels; |
53 | QMutex mutex; |
54 | }; |
55 | |
56 | } // namespace |
57 | |
58 | Q_GLOBAL_STATIC(TinyCanGlobal, gTinyCan) |
59 | |
60 | class TinyCanWriteNotifier : public QTimer |
61 | { |
62 | // no Q_OBJECT macro! |
63 | public: |
64 | TinyCanWriteNotifier(TinyCanBackendPrivate *d, QObject *parent) |
65 | : QTimer(parent) |
66 | , dptr(d) |
67 | { |
68 | } |
69 | |
70 | protected: |
71 | void timerEvent(QTimerEvent *e) override |
72 | { |
73 | if (e->timerId() == timerId()) { |
74 | dptr->startWrite(); |
75 | return; |
76 | } |
77 | QTimer::timerEvent(e); |
78 | } |
79 | |
80 | private: |
81 | TinyCanBackendPrivate * const dptr; |
82 | }; |
83 | |
84 | static int driverRefCount = 0; |
85 | |
86 | static 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 | |
100 | TinyCanBackendPrivate::TinyCanBackendPrivate(TinyCanBackend *q) |
101 | : q_ptr(q) |
102 | { |
103 | startupDriver(); |
104 | |
105 | QMutexLocker lock(&gTinyCan->mutex); |
106 | gTinyCan->channels.append(t: this); |
107 | } |
108 | |
109 | TinyCanBackendPrivate::~TinyCanBackendPrivate() |
110 | { |
111 | cleanupDriver(); |
112 | |
113 | QMutexLocker lock(&gTinyCan->mutex); |
114 | gTinyCan->channels.removeAll(t: this); |
115 | } |
116 | |
117 | struct BitrateItem |
118 | { |
119 | int bitrate; |
120 | int code; |
121 | }; |
122 | |
123 | struct BitrateLessFunctor |
124 | { |
125 | bool operator()( const BitrateItem &item1, const BitrateItem &item2) const |
126 | { |
127 | return item1.bitrate < item2.bitrate; |
128 | } |
129 | }; |
130 | |
131 | static 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 | |
152 | bool 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 | |
189 | void 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 | |
203 | bool 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. |
220 | QString 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 | |
289 | static 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 | |
299 | void TinyCanBackendPrivate::setupChannel(const QString &interfaceName) |
300 | { |
301 | channelIndex = channelIndexFromName(interfaceName); |
302 | } |
303 | |
304 | // Calls only in constructor |
305 | void TinyCanBackendPrivate::setupDefaultConfigurations() |
306 | { |
307 | Q_Q(TinyCanBackend); |
308 | |
309 | q->setConfigurationParameter(key: QCanBusDevice::BitRateKey, value: 500000); |
310 | } |
311 | |
312 | void 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! |
346 | void 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 | |
395 | void 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 | |
418 | void 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 | |
432 | void 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 | |
444 | bool 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 | |
466 | TinyCanBackend::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 | |
476 | TinyCanBackend::~TinyCanBackend() |
477 | { |
478 | close(); |
479 | delete d_ptr; |
480 | } |
481 | |
482 | bool 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 | |
508 | void TinyCanBackend::close() |
509 | { |
510 | Q_D(TinyCanBackend); |
511 | |
512 | d->close(); |
513 | |
514 | setState(QCanBusDevice::UnconnectedState); |
515 | } |
516 | |
517 | void 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 | |
525 | bool 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 |
560 | QString TinyCanBackend::interpretErrorFrame(const QCanBusFrame &errorFrame) |
561 | { |
562 | Q_UNUSED(errorFrame); |
563 | |
564 | return QString(); |
565 | } |
566 | |
567 | void TinyCanBackend::resetController() |
568 | { |
569 | Q_D(TinyCanBackend); |
570 | d->resetController(); |
571 | } |
572 | |
573 | QCanBusDeviceInfo TinyCanBackend::deviceInfo() const |
574 | { |
575 | return createDeviceInfo(QStringLiteral("tinycan" ), QStringLiteral("can0.0" ), isVirtual: false, isFlexibleDataRateCapable: false); |
576 | } |
577 | |
578 | QT_END_NAMESPACE |
579 | |