877 lines
28 KiB
C
877 lines
28 KiB
C
/*
|
|
* Linux socketCAN interface for CANopenNode.
|
|
*
|
|
* @file CO_driver.c
|
|
* @ingroup CO_driver
|
|
* @author Janez Paternoster, Martin Wagner
|
|
* @copyright 2004 - 2015 Janez Paternoster, 2017 - 2020 Neuberger Gebaeudeautomation GmbH
|
|
*
|
|
*
|
|
* This file is part of <https://github.com/CANopenNode/CANopenNode>, a CANopen Stack.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
|
|
* file except in compliance with the License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software distributed under the License is
|
|
* distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and limitations under the License.
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <syslog.h>
|
|
#include <linux/can/raw.h>
|
|
#include <linux/can/error.h>
|
|
#include <linux/net_tstamp.h>
|
|
#include <sys/socket.h>
|
|
#include <asm/socket.h>
|
|
#include <sys/eventfd.h>
|
|
#include <time.h>
|
|
|
|
#include "301/CO_driver.h"
|
|
#include "CO_error.h"
|
|
|
|
#ifndef CO_SINGLE_THREAD
|
|
pthread_mutex_t CO_EMCY_mutex = PTHREAD_MUTEX_INITIALIZER;
|
|
pthread_mutex_t CO_OD_mutex = PTHREAD_MUTEX_INITIALIZER;
|
|
#endif
|
|
|
|
#if CO_DRIVER_MULTI_INTERFACE == 0
|
|
static CO_ReturnError_t CO_CANmodule_addInterface(CO_CANmodule_t* CANmodule, int can_ifindex);
|
|
#endif
|
|
|
|
#if CO_DRIVER_MULTI_INTERFACE > 0
|
|
|
|
static const uint32_t CO_INVALID_COB_ID = 0xffffffff;
|
|
|
|
void
|
|
CO_CANsetIdentToIndex(uint32_t* lookup, uint32_t index, uint32_t identNew, uint32_t identCurrent) {
|
|
/* entry changed, remove old one */
|
|
if (identCurrent < CO_CAN_MSG_SFF_MAX_COB_ID && identNew != identCurrent) {
|
|
lookup[identCurrent] = CO_INVALID_COB_ID;
|
|
}
|
|
|
|
/* check if this COB ID is part of the table */
|
|
if (identNew > CO_CAN_MSG_SFF_MAX_COB_ID) {
|
|
return;
|
|
}
|
|
|
|
/* Special case COB ID "0" -> valid value in *xArray[0] (CO_*CAN_NMT),
|
|
* "entry unconfigured" for all others */
|
|
if (identNew == 0) {
|
|
if (index == 0) {
|
|
lookup[0] = 0;
|
|
}
|
|
} else {
|
|
lookup[identNew] = index;
|
|
}
|
|
}
|
|
|
|
static uint32_t
|
|
CO_CANgetIndexFromIdent(uint32_t* lookup, uint32_t ident) {
|
|
/* check if this COB ID is part of the table */
|
|
if (ident > CO_CAN_MSG_SFF_MAX_COB_ID) {
|
|
return CO_INVALID_COB_ID;
|
|
}
|
|
|
|
return lookup[ident];
|
|
}
|
|
|
|
#endif /* CO_DRIVER_MULTI_INTERFACE */
|
|
|
|
/* Disable socketCAN rx */
|
|
static CO_ReturnError_t
|
|
disableRx(CO_CANmodule_t* CANmodule) {
|
|
uint32_t i;
|
|
CO_ReturnError_t retval;
|
|
|
|
/* insert a filter that doesn't match any messages */
|
|
retval = CO_ERROR_NO;
|
|
for (i = 0; i < CANmodule->CANinterfaceCount; i++) {
|
|
int ret = setsockopt(CANmodule->CANinterfaces[i].fd, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0);
|
|
if (ret < 0) {
|
|
log_printf(LOG_ERR, CAN_FILTER_FAILED, CANmodule->CANinterfaces[i].ifName);
|
|
log_printf(LOG_DEBUG, DBG_ERRNO, "setsockopt()");
|
|
retval = CO_ERROR_SYSCALL;
|
|
}
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
/* Set up or update socketCAN rx filters */
|
|
static CO_ReturnError_t
|
|
setRxFilters(CO_CANmodule_t* CANmodule) {
|
|
size_t i;
|
|
int count;
|
|
CO_ReturnError_t retval;
|
|
|
|
struct can_filter rxFiltersCpy[CANmodule->rxSize];
|
|
|
|
count = 0;
|
|
/* remove unused entries ( id == 0 and mask == 0 ) as they would act as "pass all" filter */
|
|
for (i = 0; i < CANmodule->rxSize; i++) {
|
|
if ((CANmodule->rxFilter[i].can_id != 0) || (CANmodule->rxFilter[i].can_mask != 0)) {
|
|
|
|
rxFiltersCpy[count] = CANmodule->rxFilter[i];
|
|
|
|
count++;
|
|
}
|
|
}
|
|
|
|
if (count == 0) {
|
|
/* No filter is set, disable RX */
|
|
return disableRx(CANmodule);
|
|
}
|
|
|
|
retval = CO_ERROR_NO;
|
|
for (i = 0; i < CANmodule->CANinterfaceCount; i++) {
|
|
int ret = setsockopt(CANmodule->CANinterfaces[i].fd, SOL_CAN_RAW, CAN_RAW_FILTER, rxFiltersCpy,
|
|
sizeof(struct can_filter) * count);
|
|
if (ret < 0) {
|
|
log_printf(LOG_ERR, CAN_FILTER_FAILED, CANmodule->CANinterfaces[i].ifName);
|
|
log_printf(LOG_DEBUG, DBG_ERRNO, "setsockopt()");
|
|
retval = CO_ERROR_SYSCALL;
|
|
}
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
void
|
|
CO_CANsetConfigurationMode(void* CANptr) {
|
|
(void)CANptr;
|
|
/* Can't do anything because no reference to CANmodule_t is provided */
|
|
}
|
|
|
|
void
|
|
CO_CANsetNormalMode(CO_CANmodule_t* CANmodule) {
|
|
CO_ReturnError_t ret;
|
|
|
|
if (CANmodule != NULL) {
|
|
CANmodule->CANnormal = false;
|
|
ret = setRxFilters(CANmodule);
|
|
if (ret == CO_ERROR_NO) {
|
|
/* Put CAN module in normal mode */
|
|
CANmodule->CANnormal = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
CO_ReturnError_t
|
|
CO_CANmodule_init(CO_CANmodule_t* CANmodule, void* CANptr, CO_CANrx_t rxArray[], uint16_t rxSize, CO_CANtx_t txArray[],
|
|
uint16_t txSize, uint16_t CANbitRate) {
|
|
int32_t ret;
|
|
uint16_t i;
|
|
(void)CANbitRate;
|
|
|
|
/* verify arguments */
|
|
if (CANmodule == NULL || CANptr == NULL || rxArray == NULL || txArray == NULL) {
|
|
return CO_ERROR_ILLEGAL_ARGUMENT;
|
|
}
|
|
|
|
CO_CANptrSocketCan_t* CANptrReal = (CO_CANptrSocketCan_t*)CANptr;
|
|
|
|
/* Configure object variables */
|
|
CANmodule->epoll_fd = CANptrReal->epoll_fd;
|
|
CANmodule->CANinterfaces = NULL;
|
|
CANmodule->CANinterfaceCount = 0;
|
|
CANmodule->rxArray = rxArray;
|
|
CANmodule->rxSize = rxSize;
|
|
CANmodule->txArray = txArray;
|
|
CANmodule->txSize = txSize;
|
|
CANmodule->CANerrorStatus = 0;
|
|
CANmodule->CANnormal = false;
|
|
CANmodule->CANtxCount = 0;
|
|
|
|
#if CO_DRIVER_MULTI_INTERFACE > 0
|
|
for (i = 0; i < CO_CAN_MSG_SFF_MAX_COB_ID; i++) {
|
|
CANmodule->rxIdentToIndex[i] = CO_INVALID_COB_ID;
|
|
CANmodule->txIdentToIndex[i] = CO_INVALID_COB_ID;
|
|
}
|
|
#endif
|
|
|
|
/* initialize socketCAN filters. CAN module filters will be configured with
|
|
* CO_CANrxBufferInit() functions, called by separate CANopen init functions */
|
|
CANmodule->rxFilter = calloc(CANmodule->rxSize, sizeof(struct can_filter));
|
|
if (CANmodule->rxFilter == NULL) {
|
|
log_printf(LOG_DEBUG, DBG_ERRNO, "malloc()");
|
|
return CO_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
for (i = 0U; i < rxSize; i++) {
|
|
rxArray[i].ident = 0U;
|
|
rxArray[i].mask = 0xFFFFFFFFU;
|
|
rxArray[i].object = NULL;
|
|
rxArray[i].CANrx_callback = NULL;
|
|
rxArray[i].can_ifindex = 0;
|
|
rxArray[i].timestamp.tv_sec = 0;
|
|
rxArray[i].timestamp.tv_nsec = 0;
|
|
}
|
|
|
|
#if CO_DRIVER_MULTI_INTERFACE == 0
|
|
/* add one interface */
|
|
ret = CO_CANmodule_addInterface(CANmodule, CANptrReal->can_ifindex);
|
|
if (ret != CO_ERROR_NO) {
|
|
CO_CANmodule_disable(CANmodule);
|
|
return ret;
|
|
}
|
|
#endif
|
|
return CO_ERROR_NO;
|
|
}
|
|
|
|
/* enable socketCAN */
|
|
#if CO_DRIVER_MULTI_INTERFACE == 0
|
|
static
|
|
#endif
|
|
CO_ReturnError_t
|
|
CO_CANmodule_addInterface(CO_CANmodule_t* CANmodule, int can_ifindex) {
|
|
int32_t ret;
|
|
int32_t tmp;
|
|
int32_t bytes;
|
|
char* ifName;
|
|
socklen_t sLen;
|
|
CO_CANinterface_t* interface;
|
|
struct sockaddr_can sockAddr;
|
|
struct epoll_event ev = {0};
|
|
#if CO_DRIVER_ERROR_REPORTING > 0
|
|
can_err_mask_t err_mask;
|
|
#endif
|
|
|
|
if (CANmodule->CANnormal != false) {
|
|
/* can't change config now! */
|
|
return CO_ERROR_INVALID_STATE;
|
|
}
|
|
|
|
/* Add interface to interface list */
|
|
CANmodule->CANinterfaceCount++;
|
|
CANmodule->CANinterfaces = realloc(CANmodule->CANinterfaces,
|
|
((CANmodule->CANinterfaceCount) * sizeof(*CANmodule->CANinterfaces)));
|
|
if (CANmodule->CANinterfaces == NULL) {
|
|
log_printf(LOG_DEBUG, DBG_ERRNO, "malloc()");
|
|
return CO_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
interface = &CANmodule->CANinterfaces[CANmodule->CANinterfaceCount - 1];
|
|
|
|
interface->can_ifindex = can_ifindex;
|
|
ifName = if_indextoname(can_ifindex, interface->ifName);
|
|
if (ifName == NULL) {
|
|
log_printf(LOG_DEBUG, DBG_ERRNO, "if_indextoname()");
|
|
return CO_ERROR_ILLEGAL_ARGUMENT;
|
|
}
|
|
|
|
/* Create socket */
|
|
interface->fd = socket(PF_CAN, SOCK_RAW, CAN_RAW);
|
|
if (interface->fd < 0) {
|
|
log_printf(LOG_DEBUG, DBG_ERRNO, "socket(can)");
|
|
return CO_ERROR_SYSCALL;
|
|
}
|
|
|
|
/* enable socket rx queue overflow detection */
|
|
tmp = 1;
|
|
ret = setsockopt(interface->fd, SOL_SOCKET, SO_RXQ_OVFL, &tmp, sizeof(tmp));
|
|
if (ret < 0) {
|
|
log_printf(LOG_DEBUG, DBG_ERRNO, "setsockopt(ovfl)");
|
|
return CO_ERROR_SYSCALL;
|
|
}
|
|
|
|
/* enable software time stamp mode (hardware timestamps do not work properly on all devices) */
|
|
tmp = (SOF_TIMESTAMPING_SOFTWARE | SOF_TIMESTAMPING_RX_SOFTWARE);
|
|
ret = setsockopt(interface->fd, SOL_SOCKET, SO_TIMESTAMPING, &tmp, sizeof(tmp));
|
|
if (ret < 0) {
|
|
log_printf(LOG_DEBUG, DBG_ERRNO, "setsockopt(timestamping)");
|
|
return CO_ERROR_SYSCALL;
|
|
}
|
|
|
|
// todo - modify rx buffer size? first one needs root
|
|
// ret = setsockopt(fd, SOL_SOCKET, SO_RCVBUFFORCE, (void *)&bytes, sLen);
|
|
// ret = setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (void *)&bytes, sLen);
|
|
|
|
/* print socket rx buffer size in bytes (In my experience, the kernel reserves
|
|
* around 450 bytes for each CAN message) */
|
|
sLen = sizeof(bytes);
|
|
getsockopt(interface->fd, SOL_SOCKET, SO_RCVBUF, (void*)&bytes, &sLen);
|
|
if (sLen == sizeof(bytes)) {
|
|
log_printf(LOG_INFO, CAN_SOCKET_BUF_SIZE, interface->ifName, bytes / 446, bytes);
|
|
}
|
|
|
|
/* bind socket */
|
|
memset(&sockAddr, 0, sizeof(sockAddr));
|
|
sockAddr.can_family = AF_CAN;
|
|
sockAddr.can_ifindex = can_ifindex;
|
|
ret = bind(interface->fd, (struct sockaddr*)&sockAddr, sizeof(sockAddr));
|
|
if (ret < 0) {
|
|
log_printf(LOG_ERR, CAN_BINDING_FAILED, interface->ifName);
|
|
log_printf(LOG_DEBUG, DBG_ERRNO, "bind()");
|
|
return CO_ERROR_SYSCALL;
|
|
}
|
|
|
|
#if CO_DRIVER_ERROR_REPORTING > 0
|
|
CO_CANerror_init(&interface->errorhandler, interface->fd, interface->ifName);
|
|
/* set up error frame generation. What actually is available depends on your CAN kernel driver */
|
|
#ifdef DEBUG
|
|
err_mask = CAN_ERR_MASK; // enable ALL error frames
|
|
#else
|
|
err_mask = CAN_ERR_ACK | CAN_ERR_CRTL | CAN_ERR_BUSOFF | CAN_ERR_BUSERROR;
|
|
#endif
|
|
ret = setsockopt(interface->fd, SOL_CAN_RAW, CAN_RAW_ERR_FILTER, &err_mask, sizeof(err_mask));
|
|
if (ret < 0) {
|
|
log_printf(LOG_ERR, CAN_ERROR_FILTER_FAILED, interface->ifName);
|
|
log_printf(LOG_DEBUG, DBG_ERRNO, "setsockopt(can err)");
|
|
return CO_ERROR_SYSCALL;
|
|
}
|
|
#endif /* CO_DRIVER_ERROR_REPORTING */
|
|
|
|
/* Add socket to epoll */
|
|
ev.events = EPOLLIN;
|
|
ev.data.fd = interface->fd;
|
|
ret = epoll_ctl(CANmodule->epoll_fd, EPOLL_CTL_ADD, ev.data.fd, &ev);
|
|
if (ret < 0) {
|
|
log_printf(LOG_DEBUG, DBG_ERRNO, "epoll_ctl(can)");
|
|
return CO_ERROR_SYSCALL;
|
|
}
|
|
|
|
/* rx is started by calling #CO_CANsetNormalMode() */
|
|
ret = disableRx(CANmodule);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void
|
|
CO_CANmodule_disable(CO_CANmodule_t* CANmodule) {
|
|
uint32_t i;
|
|
|
|
if (CANmodule == NULL) {
|
|
return;
|
|
}
|
|
|
|
CANmodule->CANnormal = false;
|
|
|
|
/* clear interfaces */
|
|
for (i = 0; i < CANmodule->CANinterfaceCount; i++) {
|
|
CO_CANinterface_t* interface = &CANmodule->CANinterfaces[i];
|
|
|
|
#if CO_DRIVER_ERROR_REPORTING > 0
|
|
CO_CANerror_disable(&interface->errorhandler);
|
|
#endif
|
|
|
|
epoll_ctl(CANmodule->epoll_fd, EPOLL_CTL_DEL, interface->fd, NULL);
|
|
close(interface->fd);
|
|
interface->fd = -1;
|
|
}
|
|
CANmodule->CANinterfaceCount = 0;
|
|
if (CANmodule->CANinterfaces != NULL) {
|
|
free(CANmodule->CANinterfaces);
|
|
}
|
|
CANmodule->CANinterfaces = NULL;
|
|
|
|
if (CANmodule->rxFilter != NULL) {
|
|
free(CANmodule->rxFilter);
|
|
}
|
|
CANmodule->rxFilter = NULL;
|
|
}
|
|
|
|
CO_ReturnError_t
|
|
CO_CANrxBufferInit(CO_CANmodule_t* CANmodule, uint16_t index, uint16_t ident, uint16_t mask, bool_t rtr, void* object,
|
|
void (*CANrx_callback)(void* object, void* message)) {
|
|
CO_ReturnError_t ret = CO_ERROR_NO;
|
|
|
|
if ((CANmodule != NULL) && (index < CANmodule->rxSize)) {
|
|
CO_CANrx_t* buffer;
|
|
|
|
/* buffer, which will be configured */
|
|
buffer = &CANmodule->rxArray[index];
|
|
|
|
#if CO_DRIVER_MULTI_INTERFACE > 0
|
|
CO_CANsetIdentToIndex(CANmodule->rxIdentToIndex, index, ident, buffer->ident);
|
|
#endif
|
|
|
|
/* Configure object variables */
|
|
buffer->object = object;
|
|
buffer->CANrx_callback = CANrx_callback;
|
|
buffer->can_ifindex = 0;
|
|
buffer->timestamp.tv_nsec = 0;
|
|
buffer->timestamp.tv_sec = 0;
|
|
|
|
/* CAN identifier and CAN mask, bit aligned with CAN module */
|
|
buffer->ident = ident & CAN_SFF_MASK;
|
|
if (rtr) {
|
|
buffer->ident |= CAN_RTR_FLAG;
|
|
}
|
|
buffer->mask = (mask & CAN_SFF_MASK) | CAN_EFF_FLAG | CAN_RTR_FLAG;
|
|
|
|
/* Set CAN hardware module filter and mask. */
|
|
CANmodule->rxFilter[index].can_id = buffer->ident;
|
|
CANmodule->rxFilter[index].can_mask = buffer->mask;
|
|
if (CANmodule->CANnormal) {
|
|
ret = setRxFilters(CANmodule);
|
|
}
|
|
} else {
|
|
log_printf(LOG_DEBUG, DBG_CAN_RX_PARAM_FAILED, "illegal argument");
|
|
ret = CO_ERROR_ILLEGAL_ARGUMENT;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
#if CO_DRIVER_MULTI_INTERFACE > 0
|
|
|
|
bool_t
|
|
CO_CANrxBuffer_getInterface(CO_CANmodule_t* CANmodule, uint16_t ident, int* can_ifindexRx, struct timespec* timestamp) {
|
|
CO_CANrx_t* buffer;
|
|
|
|
if (CANmodule == NULL) {
|
|
return false;
|
|
}
|
|
|
|
const uint32_t index = CO_CANgetIndexFromIdent(CANmodule->rxIdentToIndex, ident);
|
|
if ((index == CO_INVALID_COB_ID) || (index > CANmodule->rxSize)) {
|
|
return false;
|
|
}
|
|
buffer = &CANmodule->rxArray[index];
|
|
|
|
/* return values */
|
|
if (can_ifindexRx != NULL) {
|
|
*can_ifindexRx = buffer->can_ifindex;
|
|
}
|
|
if (timestamp != NULL) {
|
|
*timestamp = buffer->timestamp;
|
|
}
|
|
if (buffer->can_ifindex != 0) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
#endif /* CO_DRIVER_MULTI_INTERFACE */
|
|
|
|
CO_CANtx_t*
|
|
CO_CANtxBufferInit(CO_CANmodule_t* CANmodule, uint16_t index, uint16_t ident, bool_t rtr, uint8_t noOfBytes,
|
|
bool_t syncFlag) {
|
|
CO_CANtx_t* buffer = NULL;
|
|
|
|
if ((CANmodule != NULL) && (index < CANmodule->txSize)) {
|
|
/* get specific buffer */
|
|
buffer = &CANmodule->txArray[index];
|
|
|
|
#if CO_DRIVER_MULTI_INTERFACE > 0
|
|
CO_CANsetIdentToIndex(CANmodule->txIdentToIndex, index, ident, buffer->ident);
|
|
#endif
|
|
|
|
buffer->can_ifindex = 0;
|
|
|
|
/* CAN identifier and rtr */
|
|
buffer->ident = ident & CAN_SFF_MASK;
|
|
if (rtr) {
|
|
buffer->ident |= CAN_RTR_FLAG;
|
|
}
|
|
buffer->DLC = noOfBytes;
|
|
buffer->bufferFull = false;
|
|
buffer->syncFlag = syncFlag;
|
|
}
|
|
|
|
return buffer;
|
|
}
|
|
|
|
#if CO_DRIVER_MULTI_INTERFACE > 0
|
|
|
|
CO_ReturnError_t
|
|
CO_CANtxBuffer_setInterface(CO_CANmodule_t* CANmodule, uint16_t ident, int can_ifindexTx) {
|
|
if (CANmodule != NULL) {
|
|
uint32_t index;
|
|
|
|
index = CO_CANgetIndexFromIdent(CANmodule->txIdentToIndex, ident);
|
|
if ((index == CO_INVALID_COB_ID) || (index > CANmodule->txSize)) {
|
|
return CO_ERROR_ILLEGAL_ARGUMENT;
|
|
}
|
|
CANmodule->txArray[index].can_ifindex = can_ifindexTx;
|
|
|
|
return CO_ERROR_NO;
|
|
}
|
|
return CO_ERROR_ILLEGAL_ARGUMENT;
|
|
}
|
|
|
|
#endif /* CO_DRIVER_MULTI_INTERFACE */
|
|
#if CO_DRIVER_MULTI_INTERFACE > 0
|
|
|
|
/* send CAN message */
|
|
static CO_ReturnError_t
|
|
CO_CANCheckSendInterface(CO_CANmodule_t* CANmodule, CO_CANtx_t* buffer, CO_CANinterface_t* interface) {
|
|
CO_ReturnError_t err = CO_ERROR_NO;
|
|
#if CO_DRIVER_ERROR_REPORTING > 0
|
|
CO_CANinterfaceState_t ifState;
|
|
#endif
|
|
ssize_t n;
|
|
|
|
if (CANmodule == NULL || interface == NULL || interface->fd < 0) {
|
|
return CO_ERROR_ILLEGAL_ARGUMENT;
|
|
}
|
|
|
|
#if CO_DRIVER_ERROR_REPORTING > 0
|
|
ifState = CO_CANerror_txMsg(&interface->errorhandler);
|
|
switch (ifState) {
|
|
case CO_INTERFACE_ACTIVE:
|
|
/* continue */
|
|
break;
|
|
case CO_INTERFACE_LISTEN_ONLY:
|
|
/* silently drop message */
|
|
return CO_ERROR_NO;
|
|
default: return CO_ERROR_INVALID_STATE;
|
|
}
|
|
#endif
|
|
|
|
do {
|
|
errno = 0;
|
|
n = send(interface->fd, buffer, CAN_MTU, MSG_DONTWAIT);
|
|
if (errno == EINTR) {
|
|
/* try again */
|
|
continue;
|
|
} else if (errno == EAGAIN) {
|
|
/* socket queue full */
|
|
break;
|
|
} else if (errno == ENOBUFS) {
|
|
/* socketCAN doesn't support blocking write. You can wait here for
|
|
* a few hundred us and then try again */
|
|
#if CO_DRIVER_ERROR_REPORTING > 0
|
|
interface->errorhandler.CANerrorStatus |= CO_CAN_ERRTX_OVERFLOW;
|
|
#endif
|
|
return CO_ERROR_TX_BUSY;
|
|
} else if (n != CAN_MTU) {
|
|
break;
|
|
}
|
|
} while (errno != 0);
|
|
|
|
if (n != CAN_MTU) {
|
|
#if CO_DRIVER_ERROR_REPORTING > 0
|
|
interface->errorhandler.CANerrorStatus |= CO_CAN_ERRTX_OVERFLOW;
|
|
#endif
|
|
log_printf(LOG_ERR, DBG_CAN_TX_FAILED, buffer->ident, interface->ifName);
|
|
log_printf(LOG_DEBUG, DBG_ERRNO, "send()");
|
|
err = CO_ERROR_TX_OVERFLOW;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* The same as #CO_CANsend(), but ensures that there is enough space remaining
|
|
* in the driver for more important messages.
|
|
*
|
|
* The default threshold is 50%, or at least 1 message buffer. If sending
|
|
* would violate those limits, #CO_ERROR_TX_OVERFLOW is returned and the
|
|
* message will not be sent.
|
|
*
|
|
* (It is not in header, because usage of CO_CANCheckSend() is unclear.)
|
|
*
|
|
* @param CANmodule This object.
|
|
* @param buffer Pointer to transmit buffer, returned by CO_CANtxBufferInit().
|
|
* Data bytes must be written in buffer before function call.
|
|
*
|
|
* @return #CO_ReturnError_t: CO_ERROR_NO, CO_ERROR_TX_OVERFLOW, CO_ERROR_TX_BUSY or
|
|
* CO_ERROR_TX_PDO_WINDOW (Synchronous TPDO is outside window).
|
|
*/
|
|
CO_ReturnError_t CO_CANCheckSend(CO_CANmodule_t* CANmodule, CO_CANtx_t* buffer);
|
|
|
|
CO_ReturnError_t
|
|
CO_CANsend(CO_CANmodule_t* CANmodule, CO_CANtx_t* buffer) {
|
|
CO_ReturnError_t err;
|
|
err = CO_CANCheckSend(CANmodule, buffer);
|
|
if (err == CO_ERROR_TX_BUSY) {
|
|
/* send doesn't have "busy" */
|
|
log_printf(LOG_ERR, DBG_CAN_TX_FAILED, buffer->ident, "CANx");
|
|
log_printf(LOG_DEBUG, DBG_ERRNO, "send()");
|
|
err = CO_ERROR_TX_OVERFLOW;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
CO_ReturnError_t
|
|
CO_CANCheckSend(CO_CANmodule_t* CANmodule, CO_CANtx_t* buffer) {
|
|
uint32_t i;
|
|
CO_ReturnError_t err = CO_ERROR_NO;
|
|
|
|
/* check on which interfaces to send this messages */
|
|
for (i = 0; i < CANmodule->CANinterfaceCount; i++) {
|
|
CO_CANinterface_t* interface = &CANmodule->CANinterfaces[i];
|
|
|
|
if ((buffer->can_ifindex == 0) || buffer->can_ifindex == interface->can_ifindex) {
|
|
|
|
CO_ReturnError_t tmp;
|
|
|
|
/* match, use this one */
|
|
tmp = CO_CANCheckSendInterface(CANmodule, buffer, interface);
|
|
if (tmp) {
|
|
/* only last error is returned to callee */
|
|
err = tmp;
|
|
}
|
|
}
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
#warning CO_CANsend() is outdated for CO_DRIVER_MULTI_INTERFACE > 0
|
|
|
|
#endif /* CO_DRIVER_MULTI_INTERFACE > 0 */
|
|
|
|
#if CO_DRIVER_MULTI_INTERFACE == 0
|
|
|
|
/* Change handling of tx buffer full in CO_CANsend(). Use CO_CANtx_t->bufferFull
|
|
* flag. Re-transmit undelivered message inside CO_CANmodule_process(). */
|
|
CO_ReturnError_t
|
|
CO_CANsend(CO_CANmodule_t* CANmodule, CO_CANtx_t* buffer) {
|
|
CO_ReturnError_t err = CO_ERROR_NO;
|
|
|
|
if (CANmodule == NULL || buffer == NULL || CANmodule->CANinterfaceCount == 0) {
|
|
return CO_ERROR_ILLEGAL_ARGUMENT;
|
|
}
|
|
|
|
CO_CANinterface_t* interface = &CANmodule->CANinterfaces[0];
|
|
if (interface == NULL || interface->fd < 0) {
|
|
return CO_ERROR_ILLEGAL_ARGUMENT;
|
|
}
|
|
|
|
/* Verify overflow */
|
|
if (buffer->bufferFull) {
|
|
#if CO_DRIVER_ERROR_REPORTING > 0
|
|
interface->errorhandler.CANerrorStatus |= CO_CAN_ERRTX_OVERFLOW;
|
|
#endif
|
|
log_printf(LOG_ERR, DBG_CAN_TX_FAILED, buffer->ident, interface->ifName);
|
|
err = CO_ERROR_TX_OVERFLOW;
|
|
}
|
|
|
|
errno = 0;
|
|
ssize_t n = send(interface->fd, buffer, CAN_MTU, MSG_DONTWAIT);
|
|
if (errno == 0 && n == CAN_MTU) {
|
|
/* success */
|
|
if (buffer->bufferFull) {
|
|
buffer->bufferFull = false;
|
|
CANmodule->CANtxCount--;
|
|
}
|
|
} else if (errno == EINTR || errno == EAGAIN || errno == ENOBUFS) {
|
|
/* Send failed, message will be re-sent by CO_CANmodule_process() */
|
|
if (!buffer->bufferFull) {
|
|
buffer->bufferFull = true;
|
|
CANmodule->CANtxCount++;
|
|
}
|
|
err = CO_ERROR_TX_BUSY;
|
|
} else {
|
|
/* Unknown error */
|
|
log_printf(LOG_DEBUG, DBG_ERRNO, "send()");
|
|
#if CO_DRIVER_ERROR_REPORTING > 0
|
|
interface->errorhandler.CANerrorStatus |= CO_CAN_ERRTX_OVERFLOW;
|
|
#endif
|
|
err = CO_ERROR_SYSCALL;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
#endif /* CO_DRIVER_MULTI_INTERFACE == 0 */
|
|
|
|
void
|
|
CO_CANclearPendingSyncPDOs(CO_CANmodule_t* CANmodule) {
|
|
(void)CANmodule;
|
|
/* Messages are either written to the socket queue or dropped */
|
|
}
|
|
|
|
void
|
|
CO_CANmodule_process(CO_CANmodule_t* CANmodule) {
|
|
if (CANmodule == NULL || CANmodule->CANinterfaceCount == 0) {
|
|
return;
|
|
}
|
|
|
|
#if CO_DRIVER_ERROR_REPORTING > 0
|
|
/* socketCAN doesn't support microcontroller-like error counters. If an
|
|
* error has occured, a special can message is created by the driver and
|
|
* received by the application like a regular message.
|
|
* Therefore, error counter evaluation is included in rx function.
|
|
* Here we just copy evaluated CANerrorStatus from the first CAN interface. */
|
|
|
|
CANmodule->CANerrorStatus = CANmodule->CANinterfaces[0].errorhandler.CANerrorStatus;
|
|
#endif
|
|
|
|
#if CO_DRIVER_MULTI_INTERFACE == 0
|
|
/* recall CO_CANsend(), if message was unsent before */
|
|
if (CANmodule->CANtxCount > 0) {
|
|
bool_t found = false;
|
|
|
|
for (uint16_t i = 0; i < CANmodule->txSize; i++) {
|
|
CO_CANtx_t* buffer = &CANmodule->txArray[i];
|
|
|
|
if (buffer->bufferFull) {
|
|
buffer->bufferFull = false;
|
|
CANmodule->CANtxCount--;
|
|
CO_CANsend(CANmodule, buffer);
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
CANmodule->CANtxCount = 0;
|
|
}
|
|
}
|
|
#endif /* CO_DRIVER_MULTI_INTERFACE == 0 */
|
|
}
|
|
|
|
/* Read CAN message from socket and verify some errors */
|
|
static CO_ReturnError_t
|
|
CO_CANread(CO_CANmodule_t* CANmodule, CO_CANinterface_t* interface,
|
|
struct can_frame* msg, /* CAN message, return value */
|
|
struct timespec* timestamp) /* timestamp of CAN message, return value */
|
|
{
|
|
int32_t n;
|
|
uint32_t dropped;
|
|
/* recvmsg - like read, but generates statistics about the socket example in berlios candump.c */
|
|
struct iovec iov;
|
|
struct msghdr msghdr;
|
|
char ctrlmsg[CMSG_SPACE(sizeof(struct timeval)) + CMSG_SPACE(sizeof(dropped))];
|
|
struct cmsghdr* cmsg;
|
|
|
|
iov.iov_base = msg;
|
|
iov.iov_len = sizeof(*msg);
|
|
|
|
msghdr.msg_name = NULL;
|
|
msghdr.msg_namelen = 0;
|
|
msghdr.msg_iov = &iov;
|
|
msghdr.msg_iovlen = 1;
|
|
msghdr.msg_control = &ctrlmsg;
|
|
msghdr.msg_controllen = sizeof(ctrlmsg);
|
|
msghdr.msg_flags = 0;
|
|
|
|
n = recvmsg(interface->fd, &msghdr, 0);
|
|
if (n != CAN_MTU) {
|
|
#if CO_DRIVER_ERROR_REPORTING > 0
|
|
interface->errorhandler.CANerrorStatus |= CO_CAN_ERRRX_OVERFLOW;
|
|
#endif
|
|
log_printf(LOG_DEBUG, DBG_CAN_RX_FAILED, interface->ifName);
|
|
log_printf(LOG_DEBUG, DBG_ERRNO, "recvmsg()");
|
|
return CO_ERROR_SYSCALL;
|
|
}
|
|
|
|
/* check for rx queue overflow, get rx time */
|
|
for (cmsg = CMSG_FIRSTHDR(&msghdr); cmsg && (cmsg->cmsg_level == SOL_SOCKET); cmsg = CMSG_NXTHDR(&msghdr, cmsg)) {
|
|
if (cmsg->cmsg_type == SO_TIMESTAMPING) {
|
|
/* this is system time, not monotonic time! */
|
|
*timestamp = ((struct timespec*)CMSG_DATA(cmsg))[0];
|
|
} else if (cmsg->cmsg_type == SO_RXQ_OVFL) {
|
|
dropped = *(uint32_t*)CMSG_DATA(cmsg);
|
|
if (dropped > CANmodule->rxDropCount) {
|
|
#if CO_DRIVER_ERROR_REPORTING > 0
|
|
interface->errorhandler.CANerrorStatus |= CO_CAN_ERRRX_OVERFLOW;
|
|
#endif
|
|
log_printf(LOG_ERR, CAN_RX_SOCKET_QUEUE_OVERFLOW, interface->ifName, dropped);
|
|
}
|
|
CANmodule->rxDropCount = dropped;
|
|
// todo use this info!
|
|
}
|
|
}
|
|
|
|
return CO_ERROR_NO;
|
|
}
|
|
|
|
/* find msg inside rxArray and call corresponding CANrx_callback */
|
|
static int32_t
|
|
CO_CANrxMsg( /* return index of received message in rxArray or -1 */
|
|
CO_CANmodule_t* CANmodule, struct can_frame* msg, /* CAN message input */
|
|
CO_CANrxMsg_t* buffer) /* If not NULL, msg will be copied to buffer */
|
|
{
|
|
int32_t retval;
|
|
const CO_CANrxMsg_t* rcvMsg; /* pointer to received message in CAN module */
|
|
uint16_t index; /* index of received message */
|
|
CO_CANrx_t* rcvMsgObj = NULL; /* receive message object from CO_CANmodule_t object. */
|
|
bool_t msgMatched = false;
|
|
|
|
/* CANopenNode can message is binary compatible to the socketCAN one, including the extension flags */
|
|
// msg->can_id &= CAN_EFF_MASK;
|
|
rcvMsg = (CO_CANrxMsg_t*)msg;
|
|
|
|
/* Message has been received. Search rxArray from CANmodule for the
|
|
* same CAN-ID. */
|
|
rcvMsgObj = &CANmodule->rxArray[0];
|
|
for (index = 0; index < CANmodule->rxSize; index++) {
|
|
if (((rcvMsg->ident ^ rcvMsgObj->ident) & rcvMsgObj->mask) == 0U) {
|
|
msgMatched = true;
|
|
break;
|
|
}
|
|
rcvMsgObj++;
|
|
}
|
|
if (msgMatched) {
|
|
/* Call specific function, which will process the message */
|
|
if ((rcvMsgObj != NULL) && (rcvMsgObj->CANrx_callback != NULL)) {
|
|
rcvMsgObj->CANrx_callback(rcvMsgObj->object, (void*)rcvMsg);
|
|
}
|
|
/* return message */
|
|
if (buffer != NULL) {
|
|
memcpy(buffer, rcvMsg, sizeof(*buffer));
|
|
}
|
|
retval = index;
|
|
} else {
|
|
retval = -1;
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
bool_t
|
|
CO_CANrxFromEpoll(CO_CANmodule_t* CANmodule, struct epoll_event* ev, CO_CANrxMsg_t* buffer, int32_t* msgIndex) {
|
|
if (CANmodule == NULL || ev == NULL || CANmodule->CANinterfaceCount == 0) {
|
|
return false;
|
|
}
|
|
|
|
/* Verify for epoll events in CAN socket */
|
|
for (uint32_t i = 0; i < CANmodule->CANinterfaceCount; i++) {
|
|
CO_CANinterface_t* interface = &CANmodule->CANinterfaces[i];
|
|
|
|
if (ev->data.fd == interface->fd) {
|
|
if ((ev->events & (EPOLLERR | EPOLLHUP)) != 0) {
|
|
struct can_frame msg;
|
|
/* epoll detected close/error on socket. Try to pull event */
|
|
errno = 0;
|
|
recv(ev->data.fd, &msg, sizeof(msg), MSG_DONTWAIT);
|
|
log_printf(LOG_DEBUG, DBG_CAN_RX_EPOLL, ev->events, strerror(errno));
|
|
} else if ((ev->events & EPOLLIN) != 0) {
|
|
struct can_frame msg;
|
|
struct timespec timestamp;
|
|
|
|
/* get message */
|
|
CO_ReturnError_t err = CO_CANread(CANmodule, interface, &msg, ×tamp);
|
|
|
|
if (err == CO_ERROR_NO && CANmodule->CANnormal) {
|
|
|
|
if (msg.can_id & CAN_ERR_FLAG) {
|
|
/* error msg */
|
|
#if CO_DRIVER_ERROR_REPORTING > 0
|
|
CO_CANerror_rxMsgError(&interface->errorhandler, &msg);
|
|
#endif
|
|
} else {
|
|
/* data msg */
|
|
#if CO_DRIVER_ERROR_REPORTING > 0
|
|
/* clear listenOnly and noackCounter if necessary */
|
|
CO_CANerror_rxMsg(&interface->errorhandler);
|
|
#endif
|
|
int32_t idx = CO_CANrxMsg(CANmodule, &msg, buffer);
|
|
if (idx > -1) {
|
|
/* Store message info */
|
|
CANmodule->rxArray[idx].timestamp = timestamp;
|
|
CANmodule->rxArray[idx].can_ifindex = interface->can_ifindex;
|
|
}
|
|
if (msgIndex != NULL) {
|
|
*msgIndex = idx;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
log_printf(LOG_DEBUG, DBG_EPOLL_UNKNOWN, ev->events, ev->data.fd);
|
|
}
|
|
return true;
|
|
} /* if (ev->data.fd == interface->fd) */
|
|
}
|
|
return false;
|
|
}
|