blob: fdd5aaa55976daecf08c260425f6ed8cb6f4e4b6 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2017 Denis Shienkov <denis.shienkov@gmail.com>
** Copyright (C) 2017 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
**
** This file is part of the QtSerialBus module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL3$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see http://www.qt.io/terms-conditions. For further
** information use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPLv3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or later as published by the Free
** Software Foundation and appearing in the file LICENSE.GPL included in
** the packaging of this file. Please review the following information to
** ensure the GNU General Public License version 2.0 requirements will be
** met: http://www.gnu.org/licenses/gpl-2.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "tinycanbackend.h"
#include "tinycanbackend_p.h"
#include "tinycan_symbols_p.h"
#include <QtSerialBus/qcanbusdevice.h>
#include <QtCore/qtimer.h>
#include <QtCore/qmutex.h>
#include <QtCore/qcoreevent.h>
#include <QtCore/qloggingcategory.h>
#include <algorithm>
QT_BEGIN_NAMESPACE
Q_DECLARE_LOGGING_CATEGORY(QT_CANBUS_PLUGINS_TINYCAN)
#ifndef LINK_LIBMHSTCAN
Q_GLOBAL_STATIC(QLibrary, mhstcanLibrary)
#endif
bool TinyCanBackend::canCreate(QString *errorReason)
{
#ifdef LINK_LIBMHSTCAN
return true;
#else
static bool symbolsResolved = resolveTinyCanSymbols(mhstcanLibrary());
if (Q_UNLIKELY(!symbolsResolved)) {
*errorReason = mhstcanLibrary()->errorString();
return false;
}
return true;
#endif
}
QList<QCanBusDeviceInfo> TinyCanBackend::interfaces()
{
QList<QCanBusDeviceInfo> result;
result.append(createDeviceInfo(QStringLiteral("can0.0")));
return result;
}
namespace {
struct TinyCanGlobal {
QList<TinyCanBackendPrivate *> channels;
QMutex mutex;
};
} // namespace
Q_GLOBAL_STATIC(TinyCanGlobal, gTinyCan)
class TinyCanWriteNotifier : public QTimer
{
// no Q_OBJECT macro!
public:
TinyCanWriteNotifier(TinyCanBackendPrivate *d, QObject *parent)
: QTimer(parent)
, dptr(d)
{
}
protected:
void timerEvent(QTimerEvent *e) override
{
if (e->timerId() == timerId()) {
dptr->startWrite();
return;
}
QTimer::timerEvent(e);
}
private:
TinyCanBackendPrivate * const dptr;
};
static int driverRefCount = 0;
static void DRV_CALLBACK_TYPE canRxEventCallback(quint32 index, TCanMsg *frame, qint32 count)
{
Q_UNUSED(frame);
Q_UNUSED(count);
QMutexLocker lock(&gTinyCan->mutex);
for (TinyCanBackendPrivate *p : qAsConst(gTinyCan->channels)) {
if (p->channelIndex == int(index)) {
p->startRead();
return;
}
}
}
TinyCanBackendPrivate::TinyCanBackendPrivate(TinyCanBackend *q)
: q_ptr(q)
{
startupDriver();
QMutexLocker lock(&gTinyCan->mutex);
gTinyCan->channels.append(this);
}
TinyCanBackendPrivate::~TinyCanBackendPrivate()
{
cleanupDriver();
QMutexLocker lock(&gTinyCan->mutex);
gTinyCan->channels.removeAll(this);
}
struct BitrateItem
{
int bitrate;
int code;
};
struct BitrateLessFunctor
{
bool operator()( const BitrateItem &item1, const BitrateItem &item2) const
{
return item1.bitrate < item2.bitrate;
}
};
static int bitrateCodeFromBitrate(int bitrate)
{
static const BitrateItem bitratetable[] = {
{ 10000, CAN_10K_BIT },
{ 20000, CAN_20K_BIT },
{ 50000, CAN_50K_BIT },
{ 100000, CAN_100K_BIT },
{ 125000, CAN_125K_BIT },
{ 250000, CAN_250K_BIT },
{ 500000, CAN_500K_BIT },
{ 800000, CAN_800K_BIT },
{ 1000000, CAN_1M_BIT }
};
static const BitrateItem *endtable = bitratetable + (sizeof(bitratetable) / sizeof(*bitratetable));
const BitrateItem item = { bitrate , 0 };
const BitrateItem *where = std::lower_bound(bitratetable, endtable, item, BitrateLessFunctor());
return where != endtable ? where->code : -1;
}
bool TinyCanBackendPrivate::open()
{
Q_Q(TinyCanBackend);
{
char options[] = "AutoConnect=1;AutoReopen=0";
const int ret = ::CanSetOptions(options);
if (Q_UNLIKELY(ret < 0)) {
q->setError(systemErrorString(ret), QCanBusDevice::CanBusError::ConnectionError);
return false;
}
}
{
const int ret = ::CanDeviceOpen(channelIndex, nullptr);
if (Q_UNLIKELY(ret < 0)) {
q->setError(systemErrorString(ret), QCanBusDevice::CanBusError::ConnectionError);
return false;
}
}
{
const int ret = ::CanSetMode(channelIndex, OP_CAN_START, CAN_CMD_ALL_CLEAR);
if (Q_UNLIKELY(ret < 0)) {
q->setError(systemErrorString(ret), QCanBusDevice::CanBusError::ConnectionError);
::CanDeviceClose(channelIndex);
return false;
}
}
writeNotifier = new TinyCanWriteNotifier(this, q);
writeNotifier->setInterval(0);
isOpen = true;
return true;
}
void TinyCanBackendPrivate::close()
{
Q_Q(TinyCanBackend);
delete writeNotifier;
writeNotifier = nullptr;
const int ret = ::CanDeviceClose(channelIndex);
if (Q_UNLIKELY(ret < 0))
q->setError(systemErrorString(ret), QCanBusDevice::CanBusError::ConnectionError);
isOpen = false;
}
bool TinyCanBackendPrivate::setConfigurationParameter(int key, const QVariant &value)
{
Q_Q(TinyCanBackend);
switch (key) {
case QCanBusDevice::BitRateKey:
return setBitRate(value.toInt());
default:
q->setError(TinyCanBackend::tr("Unsupported configuration key: %1").arg(key),
QCanBusDevice::ConfigurationError);
return false;
}
}
// These error codes taked from the errors.h file, which
// exists only in linux sources.
QString TinyCanBackendPrivate::systemErrorString(int errorCode)
{
switch (errorCode) {
case 0:
return TinyCanBackend::tr("No error");
case -1:
return TinyCanBackend::tr("Driver not initialized");
case -2:
return TinyCanBackend::tr("Invalid parameters values were passed");
case -3:
return TinyCanBackend::tr("Invalid index value");
case -4:
return TinyCanBackend::tr("More invalid CAN-channel");
case -5:
return TinyCanBackend::tr("General error");
case -6:
return TinyCanBackend::tr("The FIFO cannot be written");
case -7:
return TinyCanBackend::tr("The buffer cannot be written");
case -8:
return TinyCanBackend::tr("The FIFO cannot be read");
case -9:
return TinyCanBackend::tr("The buffer cannot be read");
case -10:
return TinyCanBackend::tr("Variable not found");
case -11:
return TinyCanBackend::tr("Reading of the variable does not permit");
case -12:
return TinyCanBackend::tr("Reading buffer for variable too small");
case -13:
return TinyCanBackend::tr("Writing of the variable does not permit");
case -14:
return TinyCanBackend::tr("The string/stream to be written is to majority");
case -15:
return TinyCanBackend::tr("Fell short min of value");
case -16:
return TinyCanBackend::tr("Max value crossed");
case -17:
return TinyCanBackend::tr("Access refuses");
case -18:
return TinyCanBackend::tr("Invalid value of CAN speed");
case -19:
return TinyCanBackend::tr("Invalid value of baud rate");
case -20:
return TinyCanBackend::tr("Value not put");
case -21:
return TinyCanBackend::tr("No connection to the hardware");
case -22:
return TinyCanBackend::tr("Communication error to the hardware");
case -23:
return TinyCanBackend::tr("Hardware sends wrong number of parameters");
case -24:
return TinyCanBackend::tr("Not enough main memory");
case -25:
return TinyCanBackend::tr("The system cannot provide the enough resources");
case -26:
return TinyCanBackend::tr("A system call returns with an error");
case -27:
return TinyCanBackend::tr("The main thread is occupied");
case -28:
return TinyCanBackend::tr("User allocated memory not found");
case -29:
return TinyCanBackend::tr("The main thread cannot be launched");
// the codes -30...-33 are skipped, as they belongs to sockets
default:
return TinyCanBackend::tr("Unknown error");
}
}
static int channelIndexFromName(const QString &interfaceName)
{
if (interfaceName == QStringLiteral("can0.0"))
return INDEX_CAN_KANAL_A;
else if (interfaceName == QStringLiteral("can0.1"))
return INDEX_CAN_KANAL_B;
else
return INDEX_INVALID;
}
void TinyCanBackendPrivate::setupChannel(const QString &interfaceName)
{
channelIndex = channelIndexFromName(interfaceName);
}
// Calls only in constructor
void TinyCanBackendPrivate::setupDefaultConfigurations()
{
Q_Q(TinyCanBackend);
q->setConfigurationParameter(QCanBusDevice::BitRateKey, 500000);
}
void TinyCanBackendPrivate::startWrite()
{
Q_Q(TinyCanBackend);
if (!q->hasOutgoingFrames()) {
writeNotifier->stop();
return;
}
const QCanBusFrame frame = q->dequeueOutgoingFrame();
const QByteArray payload = frame.payload();
TCanMsg message;
::memset(&message, 0, sizeof(message));
if (Q_UNLIKELY(payload.size() > int(sizeof(message.Data.Bytes)))) {
qCWarning(QT_CANBUS_PLUGINS_TINYCAN, "Cannot write frame with payload size %d.", payload.size());
} else {
message.Id = frame.frameId();
message.Flags.Flag.Len = payload.size();
message.Flags.Flag.Error = (frame.frameType() == QCanBusFrame::ErrorFrame);
message.Flags.Flag.RTR = (frame.frameType() == QCanBusFrame::RemoteRequestFrame);
message.Flags.Flag.TxD = 1;
message.Flags.Flag.EFF = frame.hasExtendedFrameFormat();
const qint32 messagesToWrite = 1;
::memcpy(message.Data.Bytes, payload.constData(), sizeof(message.Data.Bytes));
const int ret = ::CanTransmit(channelIndex, &message, messagesToWrite);
if (Q_UNLIKELY(ret < 0))
q->setError(systemErrorString(ret), QCanBusDevice::CanBusError::WriteError);
else
emit q->framesWritten(messagesToWrite);
}
if (q->hasOutgoingFrames() && !writeNotifier->isActive())
writeNotifier->start();
}
// this method is called from the different thread!
void TinyCanBackendPrivate::startRead()
{
Q_Q(TinyCanBackend);
QVector<QCanBusFrame> newFrames;
for (;;) {
if (!::CanReceiveGetCount(channelIndex))
break;
TCanMsg message;
::memset(&message, 0, sizeof(message));
const int messagesToRead = 1;
const int ret = ::CanReceive(channelIndex, &message, messagesToRead);
if (Q_UNLIKELY(ret < 0)) {
q->setError(systemErrorString(ret), QCanBusDevice::CanBusError::ReadError);
TDeviceStatus status;
::memset(&status, 0, sizeof(status));
if (::CanGetDeviceStatus(channelIndex, &status) < 0) {
q->setError(systemErrorString(ret), QCanBusDevice::CanBusError::ReadError);
} else {
if (status.CanStatus == CAN_STATUS_BUS_OFF) {
qCWarning(QT_CANBUS_PLUGINS_TINYCAN, "CAN bus is in off state, trying to reset the bus.");
resetController();
}
}
continue;
}
QCanBusFrame frame(message.Id, QByteArray(reinterpret_cast<char *>(message.Data.Bytes),
int(message.Flags.Flag.Len)));
frame.setTimeStamp(QCanBusFrame::TimeStamp(message.Time.Sec, message.Time.USec));
frame.setExtendedFrameFormat(message.Flags.Flag.EFF);
if (message.Flags.Flag.Error)
frame.setFrameType(QCanBusFrame::ErrorFrame);
else if (message.Flags.Flag.RTR)
frame.setFrameType(QCanBusFrame::RemoteRequestFrame);
else
frame.setFrameType(QCanBusFrame::DataFrame);
newFrames.append(std::move(frame));
}
q->enqueueReceivedFrames(newFrames);
}
void TinyCanBackendPrivate::startupDriver()
{
Q_Q(TinyCanBackend);
if (driverRefCount == 0) {
const int ret = ::CanInitDriver(nullptr);
if (Q_UNLIKELY(ret < 0)) {
q->setError(systemErrorString(ret), QCanBusDevice::CanBusError::ConnectionError);
return;
}
::CanSetRxEventCallback(&canRxEventCallback);
::CanSetEvents(EVENT_ENABLE_RX_MESSAGES);
} else if (Q_UNLIKELY(driverRefCount < 0)) {
qCCritical(QT_CANBUS_PLUGINS_TINYCAN, "Wrong driver reference counter: %d",
driverRefCount);
return;
}
++driverRefCount;
}
void TinyCanBackendPrivate::cleanupDriver()
{
--driverRefCount;
if (Q_UNLIKELY(driverRefCount < 0)) {
qCCritical(QT_CANBUS_PLUGINS_TINYCAN, "Wrong driver reference counter: %d",
driverRefCount);
driverRefCount = 0;
} else if (driverRefCount == 0) {
::CanSetEvents(EVENT_DISABLE_ALL);
::CanDownDriver();
}
}
void TinyCanBackendPrivate::resetController()
{
Q_Q(TinyCanBackend);
qint32 ret = ::CanSetMode(channelIndex, OP_CAN_RESET, CAN_CMD_NONE);
if (Q_UNLIKELY(ret < 0)) {
const QString errorString = systemErrorString(ret);
qCWarning(QT_CANBUS_PLUGINS_TINYCAN, "Cannot perform hardware reset: %ls",
qUtf16Printable(errorString));
q->setError(errorString, QCanBusDevice::CanBusError::ConfigurationError);
}
}
bool TinyCanBackendPrivate::setBitRate(int bitrate)
{
Q_Q(TinyCanBackend);
const int bitrateCode = bitrateCodeFromBitrate(bitrate);
if (Q_UNLIKELY(bitrateCode == -1)) {
q->setError(TinyCanBackend::tr("Unsupported bitrate value"),
QCanBusDevice::ConfigurationError);
return false;
}
if (isOpen) {
const int ret = ::CanSetSpeed(channelIndex, bitrateCode);
if (Q_UNLIKELY(ret < 0)) {
q->setError(systemErrorString(ret), QCanBusDevice::CanBusError::ConfigurationError);
return false;
}
}
return true;
}
TinyCanBackend::TinyCanBackend(const QString &name, QObject *parent)
: QCanBusDevice(parent)
, d_ptr(new TinyCanBackendPrivate(this))
{
Q_D(TinyCanBackend);
d->setupChannel(name);
d->setupDefaultConfigurations();
std::function<void()> f = std::bind(&TinyCanBackend::resetController, this);
setResetControllerFunction(f);
}
TinyCanBackend::~TinyCanBackend()
{
close();
delete d_ptr;
}
bool TinyCanBackend::open()
{
Q_D(TinyCanBackend);
if (!d->isOpen) {
if (!d->open()) {
close(); // sets UnconnectedState
return false;
}
// apply all stored configurations
const auto keys = configurationKeys();
for (int key : keys) {
const QVariant param = configurationParameter(key);
const bool success = d->setConfigurationParameter(key, param);
if (Q_UNLIKELY(!success)) {
qCWarning(QT_CANBUS_PLUGINS_TINYCAN, "Cannot apply parameter: %d with value: %ls.",
key, qUtf16Printable(param.toString()));
}
}
}
setState(QCanBusDevice::ConnectedState);
return true;
}
void TinyCanBackend::close()
{
Q_D(TinyCanBackend);
d->close();
setState(QCanBusDevice::UnconnectedState);
}
void TinyCanBackend::setConfigurationParameter(int key, const QVariant &value)
{
Q_D(TinyCanBackend);
if (d->setConfigurationParameter(key, value))
QCanBusDevice::setConfigurationParameter(key, value);
}
bool TinyCanBackend::writeFrame(const QCanBusFrame &newData)
{
Q_D(TinyCanBackend);
if (Q_UNLIKELY(state() != QCanBusDevice::ConnectedState))
return false;
if (Q_UNLIKELY(!newData.isValid())) {
setError(tr("Cannot write invalid QCanBusFrame"), QCanBusDevice::WriteError);
return false;
}
if (Q_UNLIKELY(newData.frameType() != QCanBusFrame::DataFrame
&& newData.frameType() != QCanBusFrame::RemoteRequestFrame
&& newData.frameType() != QCanBusFrame::ErrorFrame)) {
setError(tr("Unable to write a frame with unacceptable type"),
QCanBusDevice::WriteError);
return false;
}
// CAN FD frame format not supported at this stage
if (Q_UNLIKELY(newData.hasFlexibleDataRateFormat())) {
setError(tr("CAN FD frame format not supported."), QCanBusDevice::WriteError);
return false;
}
enqueueOutgoingFrame(newData);
if (!d->writeNotifier->isActive())
d->writeNotifier->start();
return true;
}
// TODO: Implement me
QString TinyCanBackend::interpretErrorFrame(const QCanBusFrame &errorFrame)
{
Q_UNUSED(errorFrame);
return QString();
}
void TinyCanBackend::resetController()
{
Q_D(TinyCanBackend);
d->resetController();
}
QT_END_NAMESPACE