blob: 0382d66ad3c0ed993618046cea043b6534da171d [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2017 Denis Shienkov <denis.shienkov@gmail.com>
** 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 "vectorcanbackend.h"
#include "vectorcanbackend_p.h"
#include "vectorcan_symbols_p.h"
#include <QtSerialBus/qcanbusdevice.h>
#include <QtCore/qtimer.h>
#include <QtCore/qcoreevent.h>
#include <QtCore/qloggingcategory.h>
#include <QtCore/qwineventnotifier.h>
#include <QtCore/qcoreapplication.h>
#include <algorithm>
QT_BEGIN_NAMESPACE
Q_DECLARE_LOGGING_CATEGORY(QT_CANBUS_PLUGINS_VECTORCAN)
#ifndef LINK_LIBVECTORCAN
Q_GLOBAL_STATIC(QLibrary, vectorcanLibrary)
#endif
bool VectorCanBackend::canCreate(QString *errorReason)
{
#ifdef LINK_LIBVECTORCAN
return true;
#else
static bool symbolsResolved = resolveVectorCanSymbols(vectorcanLibrary());
if (Q_UNLIKELY(!symbolsResolved)) {
*errorReason = vectorcanLibrary()->errorString();
return false;
}
return true;
#endif
}
QList<QCanBusDeviceInfo> VectorCanBackend::interfaces()
{
QList<QCanBusDeviceInfo> result;
if (Q_UNLIKELY(VectorCanBackendPrivate::loadDriver() != XL_SUCCESS))
return result;
XLdriverConfig config;
if (Q_UNLIKELY(::xlGetDriverConfig(&config) != XL_SUCCESS)) {
VectorCanBackendPrivate::cleanupDriver();
return result;
}
for (uint i = 0; i < config.channelCount; ++i) {
if (config.channel[i].hwType == XL_HWTYPE_NONE)
continue;
const bool isVirtual = config.channel[i].hwType == XL_HWTYPE_VIRTUAL;
const bool isFd = config.channel[i].channelCapabilities & XL_CHANNEL_FLAG_CANFD_SUPPORT;
const int channel = config.channel[i].hwChannel;
const QString name = QStringLiteral("can") + QString::number(i);
const QString serial = QString::number(config.channel[i].serialNumber);
const QString description = QLatin1String(config.channel[i].name);
result.append(std::move(createDeviceInfo(name, serial, description, channel,
isVirtual, isFd)));
}
VectorCanBackendPrivate::cleanupDriver();
return result;
}
static int driverRefCount = 0;
class VectorCanReadNotifier : public QWinEventNotifier
{
// no Q_OBJECT macro!
public:
explicit VectorCanReadNotifier(VectorCanBackendPrivate *d, QObject *parent)
: QWinEventNotifier(parent)
, dptr(d)
{
setHandle(dptr->readHandle);
}
protected:
bool event(QEvent *e) override
{
if (e->type() == QEvent::WinEventAct) {
dptr->startRead();
return true;
}
return QWinEventNotifier::event(e);
}
private:
VectorCanBackendPrivate * const dptr;
};
class VectorCanWriteNotifier : public QTimer
{
// no Q_OBJECT macro!
public:
VectorCanWriteNotifier(VectorCanBackendPrivate *d, QObject *parent)
: QTimer(parent)
, dptr(d)
{
setInterval(0);
}
protected:
void timerEvent(QTimerEvent *e) override
{
if (e->timerId() == timerId()) {
dptr->startWrite();
return;
}
QTimer::timerEvent(e);
}
private:
VectorCanBackendPrivate * const dptr;
};
VectorCanBackendPrivate::VectorCanBackendPrivate(VectorCanBackend *q)
: q_ptr(q)
{
startupDriver();
}
VectorCanBackendPrivate::~VectorCanBackendPrivate()
{
cleanupDriver();
}
bool VectorCanBackendPrivate::open()
{
Q_Q(VectorCanBackend);
{
XLaccess permissionMask = channelMask;
const quint32 queueSize = 256;
const XLstatus status = ::xlOpenPort(&portHandle,
const_cast<char *>(qPrintable(qApp->applicationName())),
channelMask, &permissionMask, queueSize,
XL_INTERFACE_VERSION, XL_BUS_TYPE_CAN);
if (Q_UNLIKELY(status != XL_SUCCESS || portHandle == XL_INVALID_PORTHANDLE)) {
q->setError(systemErrorString(status), QCanBusDevice::ConnectionError);
portHandle = XL_INVALID_PORTHANDLE;
return false;
}
}
{
const XLstatus status = ::xlActivateChannel(portHandle, channelMask,
XL_BUS_TYPE_CAN, XL_ACTIVATE_RESET_CLOCK);
if (Q_UNLIKELY(status != XL_SUCCESS)) {
q->setError(systemErrorString(status), QCanBusDevice::CanBusError::ConnectionError);
return false;
}
}
{
const int queueLevel = 1;
const XLstatus status = ::xlSetNotification(portHandle, &readHandle, queueLevel);
if (Q_UNLIKELY(status != XL_SUCCESS)) {
q->setError(systemErrorString(status), QCanBusDevice::ConnectionError);
return false;
}
}
readNotifier = new VectorCanReadNotifier(this, q);
readNotifier->setEnabled(true);
writeNotifier = new VectorCanWriteNotifier(this, q);
return true;
}
void VectorCanBackendPrivate::close()
{
Q_Q(VectorCanBackend);
delete readNotifier;
readNotifier = nullptr;
delete writeNotifier;
writeNotifier = nullptr;
// xlClosePort can crash on systems with vxlapi.dll but no device driver installed.
// Therefore avoid calling any close function when the portHandle is invalid anyway.
if (portHandle == XL_INVALID_PORTHANDLE)
return;
{
const XLstatus status = ::xlDeactivateChannel(portHandle, channelMask);
if (Q_UNLIKELY(status != XL_SUCCESS)) {
q->setError(systemErrorString(status), QCanBusDevice::CanBusError::ConnectionError);
}
}
{
const XLstatus status = ::xlClosePort(portHandle);
if (Q_UNLIKELY(status != XL_SUCCESS)) {
q->setError(systemErrorString(status), QCanBusDevice::ConnectionError);
}
}
portHandle = XL_INVALID_PORTHANDLE;
}
bool VectorCanBackendPrivate::setConfigurationParameter(int key, const QVariant &value)
{
Q_Q(VectorCanBackend);
switch (key) {
case QCanBusDevice::BitRateKey:
return setBitRate(value.toUInt());
case QCanBusDevice::ReceiveOwnKey:
transmitEcho = value.toBool();
return true;
default:
q->setError(VectorCanBackend::tr("Unsupported configuration key"),
QCanBusDevice::ConfigurationError);
return false;
}
}
void VectorCanBackendPrivate::setupChannel(const QString &interfaceName)
{
if (Q_LIKELY(interfaceName.startsWith(QStringLiteral("can")))) {
const QStringRef ref = interfaceName.midRef(3);
bool ok = false;
const int channelIndex = ref.toInt(&ok);
if (ok && (channelIndex >= 0 && channelIndex < XL_CONFIG_MAX_CHANNELS)) {
channelMask = XL_CHANNEL_MASK((channelIndex));
return;
}
}
qCCritical(QT_CANBUS_PLUGINS_VECTORCAN, "Unable to parse the channel %ls",
qUtf16Printable(interfaceName));
}
void VectorCanBackendPrivate::setupDefaultConfigurations()
{
Q_Q(VectorCanBackend);
q->setConfigurationParameter(QCanBusDevice::BitRateKey, 500000);
}
QString VectorCanBackendPrivate::systemErrorString(int errorCode) const
{
const char *string = ::xlGetErrorString(errorCode);
if (Q_LIKELY(string))
return QString::fromUtf8(string);
return VectorCanBackend::tr("Unable to retrieve an error string");
}
void VectorCanBackendPrivate::startWrite()
{
Q_Q(VectorCanBackend);
if (!q->hasOutgoingFrames()) {
writeNotifier->stop();
return;
}
const QCanBusFrame frame = q->dequeueOutgoingFrame();
const QByteArray payload = frame.payload();
XLevent event;
::memset(&event, 0, sizeof(event));
event.tag = XL_TRANSMIT_MSG;
s_xl_can_msg &msg = event.tagData.msg;
msg.id = frame.frameId();
if (frame.hasExtendedFrameFormat())
msg.id |= XL_CAN_EXT_MSG_ID;
msg.dlc = payload.size();
if (frame.frameType() == QCanBusFrame::RemoteRequestFrame)
msg.flags |= XL_CAN_MSG_FLAG_REMOTE_FRAME; // we do not care about the payload
else if (frame.frameType() == QCanBusFrame::ErrorFrame)
msg.flags |= XL_CAN_MSG_FLAG_ERROR_FRAME; // we do not care about the payload
else
::memcpy(msg.data, payload.constData(), sizeof(msg.data));
quint32 eventCount = 1;
const XLstatus status = ::xlCanTransmit(portHandle, channelMask,
&eventCount, &event);
if (Q_UNLIKELY(status != XL_SUCCESS)) {
q->setError(systemErrorString(status),
QCanBusDevice::WriteError);
} else {
emit q->framesWritten(qint64(eventCount));
}
if (q->hasOutgoingFrames())
writeNotifier->start();
}
void VectorCanBackendPrivate::startRead()
{
Q_Q(VectorCanBackend);
QVector<QCanBusFrame> newFrames;
for (;;) {
quint32 eventCount = 1;
XLevent event;
::memset(&event, 0, sizeof(event));
const XLstatus status = ::xlReceive(portHandle, &eventCount, &event);
if (Q_UNLIKELY(status != XL_SUCCESS)) {
if (status != XL_ERR_QUEUE_IS_EMPTY) {
q->setError(systemErrorString(status),
QCanBusDevice::ReadError);
}
break;
}
if (event.tag != XL_RECEIVE_MSG)
continue;
const s_xl_can_msg &msg = event.tagData.msg;
if ((msg.flags & XL_CAN_MSG_FLAG_TX_COMPLETED) && !transmitEcho)
continue;
QCanBusFrame frame(msg.id & ~XL_CAN_EXT_MSG_ID,
QByteArray(reinterpret_cast<const char *>(msg.data), int(msg.dlc)));
frame.setTimeStamp(QCanBusFrame::TimeStamp::fromMicroSeconds(event.timeStamp / 1000));
frame.setExtendedFrameFormat(msg.id & XL_CAN_EXT_MSG_ID);
frame.setLocalEcho(msg.flags & XL_CAN_MSG_FLAG_TX_COMPLETED);
frame.setFrameType((msg.flags & XL_CAN_MSG_FLAG_REMOTE_FRAME)
? QCanBusFrame::RemoteRequestFrame
: (msg.flags & XL_CAN_MSG_FLAG_ERROR_FRAME)
? QCanBusFrame::ErrorFrame
: QCanBusFrame::DataFrame);
newFrames.append(std::move(frame));
}
q->enqueueReceivedFrames(newFrames);
}
XLstatus VectorCanBackendPrivate::loadDriver()
{
if (driverRefCount == 0) {
const XLstatus status = ::xlOpenDriver();
if (Q_UNLIKELY(status != XL_SUCCESS))
return status;
} else if (Q_UNLIKELY(driverRefCount < 0)) {
qCCritical(QT_CANBUS_PLUGINS_VECTORCAN, "Wrong driver reference counter: %d",
driverRefCount);
return XL_ERR_CANNOT_OPEN_DRIVER;
}
++driverRefCount;
return XL_SUCCESS;
}
void VectorCanBackendPrivate::startupDriver()
{
Q_Q(VectorCanBackend);
const XLstatus status = loadDriver();
if (Q_UNLIKELY(status != XL_SUCCESS)) {
q->setError(systemErrorString(status),
QCanBusDevice::CanBusError::ConnectionError);
}
}
void VectorCanBackendPrivate::cleanupDriver()
{
--driverRefCount;
if (Q_UNLIKELY(driverRefCount < 0)) {
qCCritical(QT_CANBUS_PLUGINS_VECTORCAN, "Wrong driver reference counter: %d",
driverRefCount);
driverRefCount = 0;
} else if (driverRefCount == 0) {
::xlCloseDriver();
}
}
bool VectorCanBackendPrivate::setBitRate(quint32 bitrate)
{
Q_Q(VectorCanBackend);
if (q->state() != QCanBusDevice::UnconnectedState) {
const XLstatus status = ::xlCanSetChannelBitrate(portHandle, channelMask, bitrate);
if (Q_UNLIKELY(status != XL_SUCCESS)) {
q->setError(systemErrorString(status),
QCanBusDevice::CanBusError::ConfigurationError);
return false;
}
}
return true;
}
VectorCanBackend::VectorCanBackend(const QString &name, QObject *parent)
: QCanBusDevice(parent)
, d_ptr(new VectorCanBackendPrivate(this))
{
Q_D(VectorCanBackend);
d->setupChannel(name);
d->setupDefaultConfigurations();
std::function<CanBusStatus()> g = std::bind(&VectorCanBackend::busStatus, this);
setCanBusStatusGetter(g);
}
VectorCanBackend::~VectorCanBackend()
{
if (state() == ConnectedState)
close();
delete d_ptr;
}
bool VectorCanBackend::open()
{
Q_D(VectorCanBackend);
if (!d->open()) {
close(); // sets UnconnectedState
return false;
}
const auto keys = configurationKeys();
for (int key : keys) {
const QVariant param = configurationParameter(key);
const bool success = d->setConfigurationParameter(key, param);
if (!success) {
qCWarning(QT_CANBUS_PLUGINS_VECTORCAN, "Cannot apply parameter: %d with value: %ls.",
key, qUtf16Printable(param.toString()));
}
}
setState(QCanBusDevice::ConnectedState);
return true;
}
void VectorCanBackend::close()
{
Q_D(VectorCanBackend);
d->close();
setState(QCanBusDevice::UnconnectedState);
}
void VectorCanBackend::setConfigurationParameter(int key, const QVariant &value)
{
Q_D(VectorCanBackend);
if (d->setConfigurationParameter(key, value))
QCanBusDevice::setConfigurationParameter(key, value);
}
bool VectorCanBackend::writeFrame(const QCanBusFrame &newData)
{
Q_D(VectorCanBackend);
if (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 implemented 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 VectorCanBackend::interpretErrorFrame(const QCanBusFrame &errorFrame)
{
Q_UNUSED(errorFrame);
return QString();
}
QCanBusDevice::CanBusStatus VectorCanBackend::busStatus()
{
Q_D(VectorCanBackend);
const XLstatus requestStatus = ::xlCanRequestChipState(d->portHandle, d->channelMask);
if (Q_UNLIKELY(requestStatus != XL_SUCCESS)) {
const QString errorString = d->systemErrorString(requestStatus);
qCWarning(QT_CANBUS_PLUGINS_VECTORCAN, "Can not query CAN bus status: %ls.",
qUtf16Printable(errorString));
setError(errorString, QCanBusDevice::CanBusError::ReadError);
return QCanBusDevice::CanBusStatus::Unknown;
}
quint32 eventCount = 1;
XLevent event;
::memset(&event, 0, sizeof(event));
const XLstatus receiveStatus = ::xlReceive(d->portHandle, &eventCount, &event);
if (Q_UNLIKELY(receiveStatus != XL_SUCCESS)) {
const QString errorString = d->systemErrorString(receiveStatus);
qCWarning(QT_CANBUS_PLUGINS_VECTORCAN, "Can not query CAN bus status: %ls.",
qUtf16Printable(errorString));
setError(errorString, QCanBusDevice::CanBusError::ReadError);
return QCanBusDevice::CanBusStatus::Unknown;
}
if (Q_LIKELY(event.tag == XL_CHIP_STATE)) {
switch (event.tagData.chipState.busStatus) {
case XL_CHIPSTAT_BUSOFF:
return QCanBusDevice::CanBusStatus::BusOff;
case XL_CHIPSTAT_ERROR_PASSIVE:
return QCanBusDevice::CanBusStatus::Error;
case XL_CHIPSTAT_ERROR_WARNING:
return QCanBusDevice::CanBusStatus::Warning;
case XL_CHIPSTAT_ERROR_ACTIVE:
return QCanBusDevice::CanBusStatus::Good;
}
}
qCWarning(QT_CANBUS_PLUGINS_VECTORCAN, "Unknown CAN bus status: %u",
uint(event.tagData.chipState.busStatus));
return QCanBusDevice::CanBusStatus::Unknown;
}
QT_END_NAMESPACE