You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
553 lines
13 KiB
553 lines
13 KiB
/* |
|
* Copyright (c) 2019 Vestas Wind Systems A/S |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
#include <zephyr/kernel.h> |
|
#include <zephyr/drivers/can.h> |
|
#include <zephyr/init.h> |
|
#include <zephyr/sys/util.h> |
|
|
|
#include <canopennode.h> |
|
|
|
#define LOG_LEVEL CONFIG_CANOPEN_LOG_LEVEL |
|
#include <zephyr/logging/log.h> |
|
LOG_MODULE_REGISTER(canopen_driver); |
|
|
|
K_KERNEL_STACK_DEFINE(canopen_tx_workq_stack, |
|
CONFIG_CANOPENNODE_TX_WORKQUEUE_STACK_SIZE); |
|
|
|
struct k_work_q canopen_tx_workq; |
|
|
|
struct canopen_tx_work_container { |
|
struct k_work work; |
|
CO_CANmodule_t *CANmodule; |
|
}; |
|
|
|
struct canopen_tx_work_container canopen_tx_queue; |
|
|
|
K_MUTEX_DEFINE(canopen_send_mutex); |
|
K_MUTEX_DEFINE(canopen_emcy_mutex); |
|
K_MUTEX_DEFINE(canopen_co_mutex); |
|
|
|
static canopen_rxmsg_callback_t rxmsg_callback; |
|
|
|
inline void canopen_send_lock(void) |
|
{ |
|
k_mutex_lock(&canopen_send_mutex, K_FOREVER); |
|
} |
|
|
|
inline void canopen_send_unlock(void) |
|
{ |
|
k_mutex_unlock(&canopen_send_mutex); |
|
} |
|
|
|
inline void canopen_emcy_lock(void) |
|
{ |
|
k_mutex_lock(&canopen_emcy_mutex, K_FOREVER); |
|
} |
|
|
|
inline void canopen_emcy_unlock(void) |
|
{ |
|
k_mutex_unlock(&canopen_emcy_mutex); |
|
} |
|
|
|
inline void canopen_od_lock(void) |
|
{ |
|
k_mutex_lock(&canopen_co_mutex, K_FOREVER); |
|
} |
|
|
|
inline void canopen_od_unlock(void) |
|
{ |
|
k_mutex_unlock(&canopen_co_mutex); |
|
} |
|
|
|
void canopen_set_rxmsg_callback(canopen_rxmsg_callback_t callback) |
|
{ |
|
rxmsg_callback = callback; |
|
} |
|
|
|
static void canopen_detach_all_rx_filters(CO_CANmodule_t *CANmodule) |
|
{ |
|
uint16_t i; |
|
|
|
if (!CANmodule || !CANmodule->rx_array || !CANmodule->configured) { |
|
return; |
|
} |
|
|
|
for (i = 0U; i < CANmodule->rx_size; i++) { |
|
if (CANmodule->rx_array[i].filter_id != -ENOSPC) { |
|
can_remove_rx_filter(CANmodule->dev, |
|
CANmodule->rx_array[i].filter_id); |
|
CANmodule->rx_array[i].filter_id = -ENOSPC; |
|
} |
|
} |
|
} |
|
|
|
static void canopen_rx_callback(const struct device *dev, struct can_frame *frame, void *user_data) |
|
{ |
|
CO_CANmodule_t *CANmodule = (CO_CANmodule_t *)user_data; |
|
CO_CANrxMsg_t rxMsg; |
|
CO_CANrx_t *buffer; |
|
canopen_rxmsg_callback_t callback = rxmsg_callback; |
|
int i; |
|
|
|
ARG_UNUSED(dev); |
|
|
|
/* Loop through registered rx buffers in priority order */ |
|
for (i = 0; i < CANmodule->rx_size; i++) { |
|
buffer = &CANmodule->rx_array[i]; |
|
|
|
if (buffer->filter_id == -ENOSPC || buffer->pFunct == NULL) { |
|
continue; |
|
} |
|
|
|
if (((frame->id ^ buffer->ident) & buffer->mask) == 0U) { |
|
#ifdef CONFIG_CAN_ACCEPT_RTR |
|
if (buffer->rtr && ((frame->flags & CAN_FRAME_RTR) == 0U)) { |
|
continue; |
|
} |
|
#endif /* CONFIG_CAN_ACCEPT_RTR */ |
|
rxMsg.ident = frame->id; |
|
rxMsg.DLC = frame->dlc; |
|
memcpy(rxMsg.data, frame->data, frame->dlc); |
|
buffer->pFunct(buffer->object, &rxMsg); |
|
if (callback != NULL) { |
|
callback(); |
|
} |
|
break; |
|
} |
|
} |
|
} |
|
|
|
static void canopen_tx_callback(const struct device *dev, int error, void *arg) |
|
{ |
|
CO_CANmodule_t *CANmodule = arg; |
|
|
|
ARG_UNUSED(dev); |
|
|
|
if (!CANmodule) { |
|
LOG_ERR("failed to process CAN tx callback"); |
|
return; |
|
} |
|
|
|
if (error == 0) { |
|
CANmodule->first_tx_msg = false; |
|
} |
|
|
|
k_work_submit_to_queue(&canopen_tx_workq, &canopen_tx_queue.work); |
|
} |
|
|
|
static void canopen_tx_retry(struct k_work *item) |
|
{ |
|
struct canopen_tx_work_container *container = |
|
CONTAINER_OF(item, struct canopen_tx_work_container, work); |
|
CO_CANmodule_t *CANmodule = container->CANmodule; |
|
struct can_frame frame; |
|
CO_CANtx_t *buffer; |
|
int err; |
|
uint16_t i; |
|
|
|
memset(&frame, 0, sizeof(frame)); |
|
|
|
CO_LOCK_CAN_SEND(); |
|
|
|
for (i = 0; i < CANmodule->tx_size; i++) { |
|
buffer = &CANmodule->tx_array[i]; |
|
if (buffer->bufferFull) { |
|
frame.id = buffer->ident; |
|
frame.dlc = buffer->DLC; |
|
frame.flags |= (buffer->rtr ? CAN_FRAME_RTR : 0); |
|
memcpy(frame.data, buffer->data, buffer->DLC); |
|
|
|
err = can_send(CANmodule->dev, &frame, K_NO_WAIT, |
|
canopen_tx_callback, CANmodule); |
|
if (err == -EAGAIN) { |
|
break; |
|
} else if (err != 0) { |
|
LOG_ERR("failed to send CAN frame (err %d)", |
|
err); |
|
CO_errorReport(CANmodule->em, |
|
CO_EM_GENERIC_SOFTWARE_ERROR, |
|
CO_EMC_COMMUNICATION, 0); |
|
|
|
} |
|
|
|
buffer->bufferFull = false; |
|
} |
|
} |
|
|
|
CO_UNLOCK_CAN_SEND(); |
|
} |
|
|
|
void CO_CANsetConfigurationMode(void *CANdriverState) |
|
{ |
|
struct canopen_context *ctx = (struct canopen_context *)CANdriverState; |
|
int err; |
|
|
|
err = can_stop(ctx->dev); |
|
if (err != 0 && err != -EALREADY) { |
|
LOG_ERR("failed to stop CAN interface (err %d)", err); |
|
} |
|
} |
|
|
|
void CO_CANsetNormalMode(CO_CANmodule_t *CANmodule) |
|
{ |
|
int err; |
|
|
|
err = can_start(CANmodule->dev); |
|
if (err != 0 && err != -EALREADY) { |
|
LOG_ERR("failed to start CAN interface (err %d)", err); |
|
return; |
|
} |
|
|
|
CANmodule->CANnormal = true; |
|
} |
|
|
|
CO_ReturnError_t CO_CANmodule_init(CO_CANmodule_t *CANmodule, |
|
void *CANdriverState, |
|
CO_CANrx_t rxArray[], uint16_t rxSize, |
|
CO_CANtx_t txArray[], uint16_t txSize, |
|
uint16_t CANbitRate) |
|
{ |
|
struct canopen_context *ctx = (struct canopen_context *)CANdriverState; |
|
uint16_t i; |
|
int err; |
|
int max_filters; |
|
|
|
LOG_DBG("rxSize = %d, txSize = %d", rxSize, txSize); |
|
|
|
if (!CANmodule || !rxArray || !txArray || !CANdriverState) { |
|
LOG_ERR("failed to initialize CAN module"); |
|
return CO_ERROR_ILLEGAL_ARGUMENT; |
|
} |
|
|
|
max_filters = can_get_max_filters(ctx->dev, false); |
|
if (max_filters != -ENOSYS) { |
|
if (max_filters < 0) { |
|
LOG_ERR("unable to determine number of CAN RX filters"); |
|
return CO_ERROR_SYSCALL; |
|
} |
|
|
|
if (rxSize > max_filters) { |
|
LOG_ERR("insufficient number of concurrent CAN RX filters" |
|
" (needs %d, %d available)", rxSize, max_filters); |
|
return CO_ERROR_OUT_OF_MEMORY; |
|
} else if (rxSize < max_filters) { |
|
LOG_DBG("excessive number of concurrent CAN RX filters enabled" |
|
" (needs %d, %d available)", rxSize, max_filters); |
|
} |
|
} |
|
|
|
canopen_detach_all_rx_filters(CANmodule); |
|
canopen_tx_queue.CANmodule = CANmodule; |
|
|
|
CANmodule->dev = ctx->dev; |
|
CANmodule->rx_array = rxArray; |
|
CANmodule->rx_size = rxSize; |
|
CANmodule->tx_array = txArray; |
|
CANmodule->tx_size = txSize; |
|
CANmodule->CANnormal = false; |
|
CANmodule->first_tx_msg = true; |
|
CANmodule->errors = 0; |
|
CANmodule->em = NULL; |
|
|
|
for (i = 0U; i < rxSize; i++) { |
|
rxArray[i].ident = 0U; |
|
rxArray[i].pFunct = NULL; |
|
rxArray[i].filter_id = -ENOSPC; |
|
} |
|
|
|
for (i = 0U; i < txSize; i++) { |
|
txArray[i].bufferFull = false; |
|
} |
|
|
|
err = can_set_bitrate(CANmodule->dev, KHZ(CANbitRate)); |
|
if (err) { |
|
LOG_ERR("failed to configure CAN bitrate (err %d)", err); |
|
return CO_ERROR_ILLEGAL_ARGUMENT; |
|
} |
|
|
|
err = can_set_mode(CANmodule->dev, CAN_MODE_NORMAL); |
|
if (err) { |
|
LOG_ERR("failed to configure CAN interface (err %d)", err); |
|
return CO_ERROR_ILLEGAL_ARGUMENT; |
|
} |
|
|
|
CANmodule->configured = true; |
|
|
|
return CO_ERROR_NO; |
|
} |
|
|
|
void CO_CANmodule_disable(CO_CANmodule_t *CANmodule) |
|
{ |
|
int err; |
|
|
|
if (!CANmodule || !CANmodule->dev) { |
|
return; |
|
} |
|
|
|
canopen_detach_all_rx_filters(CANmodule); |
|
|
|
err = can_stop(CANmodule->dev); |
|
if (err != 0 && err != -EALREADY) { |
|
LOG_ERR("failed to disable CAN interface (err %d)", err); |
|
} |
|
} |
|
|
|
uint16_t CO_CANrxMsg_readIdent(const CO_CANrxMsg_t *rxMsg) |
|
{ |
|
return rxMsg->ident; |
|
} |
|
|
|
CO_ReturnError_t CO_CANrxBufferInit(CO_CANmodule_t *CANmodule, uint16_t index, |
|
uint16_t ident, uint16_t mask, bool_t rtr, |
|
void *object, |
|
CO_CANrxBufferCallback_t pFunct) |
|
{ |
|
struct can_filter filter; |
|
CO_CANrx_t *buffer; |
|
|
|
if (CANmodule == NULL) { |
|
return CO_ERROR_ILLEGAL_ARGUMENT; |
|
} |
|
|
|
if (!pFunct || (index >= CANmodule->rx_size)) { |
|
LOG_ERR("failed to initialize CAN rx buffer, illegal argument"); |
|
CO_errorReport(CANmodule->em, CO_EM_GENERIC_SOFTWARE_ERROR, |
|
CO_EMC_SOFTWARE_INTERNAL, 0); |
|
return CO_ERROR_ILLEGAL_ARGUMENT; |
|
} |
|
|
|
buffer = &CANmodule->rx_array[index]; |
|
buffer->object = object; |
|
buffer->pFunct = pFunct; |
|
buffer->ident = ident; |
|
buffer->mask = mask; |
|
|
|
#ifndef CONFIG_CAN_ACCEPT_RTR |
|
if (rtr) { |
|
LOG_ERR("request for RTR frames, but RTR frames are rejected"); |
|
CO_errorReport(CANmodule->em, CO_EM_GENERIC_SOFTWARE_ERROR, |
|
CO_EMC_SOFTWARE_INTERNAL, 0); |
|
return CO_ERROR_ILLEGAL_ARGUMENT; |
|
} |
|
#else /* !CONFIG_CAN_ACCEPT_RTR */ |
|
buffer->rtr = rtr; |
|
#endif /* CONFIG_CAN_ACCEPT_RTR */ |
|
|
|
filter.flags = 0U; |
|
filter.id = ident; |
|
filter.mask = mask; |
|
|
|
if (buffer->filter_id != -ENOSPC) { |
|
can_remove_rx_filter(CANmodule->dev, buffer->filter_id); |
|
} |
|
|
|
buffer->filter_id = can_add_rx_filter(CANmodule->dev, |
|
canopen_rx_callback, |
|
CANmodule, &filter); |
|
if (buffer->filter_id == -ENOSPC) { |
|
LOG_ERR("failed to add CAN rx callback, no free filter"); |
|
CO_errorReport(CANmodule->em, CO_EM_MEMORY_ALLOCATION_ERROR, |
|
CO_EMC_SOFTWARE_INTERNAL, 0); |
|
return CO_ERROR_OUT_OF_MEMORY; |
|
} |
|
|
|
return CO_ERROR_NO; |
|
} |
|
|
|
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; |
|
|
|
if (CANmodule == NULL) { |
|
return NULL; |
|
} |
|
|
|
if (index >= CANmodule->tx_size) { |
|
LOG_ERR("failed to initialize CAN rx buffer, illegal argument"); |
|
CO_errorReport(CANmodule->em, CO_EM_GENERIC_SOFTWARE_ERROR, |
|
CO_EMC_SOFTWARE_INTERNAL, 0); |
|
return NULL; |
|
} |
|
|
|
buffer = &CANmodule->tx_array[index]; |
|
buffer->ident = ident; |
|
buffer->rtr = rtr; |
|
buffer->DLC = noOfBytes; |
|
buffer->bufferFull = false; |
|
buffer->syncFlag = syncFlag; |
|
|
|
return buffer; |
|
} |
|
|
|
CO_ReturnError_t CO_CANsend(CO_CANmodule_t *CANmodule, CO_CANtx_t *buffer) |
|
{ |
|
CO_ReturnError_t ret = CO_ERROR_NO; |
|
struct can_frame frame; |
|
int err; |
|
|
|
if (!CANmodule || !CANmodule->dev || !buffer) { |
|
return CO_ERROR_ILLEGAL_ARGUMENT; |
|
} |
|
|
|
memset(&frame, 0, sizeof(frame)); |
|
|
|
CO_LOCK_CAN_SEND(); |
|
|
|
if (buffer->bufferFull) { |
|
if (!CANmodule->first_tx_msg) { |
|
CO_errorReport(CANmodule->em, CO_EM_CAN_TX_OVERFLOW, |
|
CO_EMC_CAN_OVERRUN, buffer->ident); |
|
} |
|
buffer->bufferFull = false; |
|
ret = CO_ERROR_TX_OVERFLOW; |
|
} |
|
|
|
frame.id = buffer->ident; |
|
frame.dlc = buffer->DLC; |
|
frame.flags = (buffer->rtr ? CAN_FRAME_RTR : 0); |
|
memcpy(frame.data, buffer->data, buffer->DLC); |
|
|
|
err = can_send(CANmodule->dev, &frame, K_NO_WAIT, canopen_tx_callback, |
|
CANmodule); |
|
if (err == -EAGAIN) { |
|
buffer->bufferFull = true; |
|
} else if (err != 0) { |
|
LOG_ERR("failed to send CAN frame (err %d)", err); |
|
CO_errorReport(CANmodule->em, CO_EM_GENERIC_SOFTWARE_ERROR, |
|
CO_EMC_COMMUNICATION, 0); |
|
ret = CO_ERROR_TX_UNCONFIGURED; |
|
} |
|
|
|
CO_UNLOCK_CAN_SEND(); |
|
|
|
return ret; |
|
} |
|
|
|
void CO_CANclearPendingSyncPDOs(CO_CANmodule_t *CANmodule) |
|
{ |
|
bool_t tpdoDeleted = false; |
|
CO_CANtx_t *buffer; |
|
uint16_t i; |
|
|
|
if (!CANmodule) { |
|
return; |
|
} |
|
|
|
CO_LOCK_CAN_SEND(); |
|
|
|
for (i = 0; i < CANmodule->tx_size; i++) { |
|
buffer = &CANmodule->tx_array[i]; |
|
if (buffer->bufferFull && buffer->syncFlag) { |
|
buffer->bufferFull = false; |
|
tpdoDeleted = true; |
|
} |
|
} |
|
|
|
CO_UNLOCK_CAN_SEND(); |
|
|
|
if (tpdoDeleted) { |
|
CO_errorReport(CANmodule->em, CO_EM_TPDO_OUTSIDE_WINDOW, |
|
CO_EMC_COMMUNICATION, 0); |
|
} |
|
} |
|
|
|
void CO_CANverifyErrors(CO_CANmodule_t *CANmodule) |
|
{ |
|
CO_EM_t *em = (CO_EM_t *)CANmodule->em; |
|
struct can_bus_err_cnt err_cnt; |
|
enum can_state state; |
|
uint8_t rx_overflows; |
|
uint32_t errors; |
|
int err; |
|
|
|
/* |
|
* TODO: Zephyr lacks an API for reading the rx mailbox |
|
* overflow counter. |
|
*/ |
|
rx_overflows = 0; |
|
|
|
err = can_get_state(CANmodule->dev, &state, &err_cnt); |
|
if (err != 0) { |
|
LOG_ERR("failed to get CAN controller state (err %d)", err); |
|
return; |
|
} |
|
|
|
errors = ((uint32_t)err_cnt.tx_err_cnt << 16) | |
|
((uint32_t)err_cnt.rx_err_cnt << 8) | |
|
rx_overflows; |
|
|
|
if (errors != CANmodule->errors) { |
|
CANmodule->errors = errors; |
|
|
|
if (state == CAN_STATE_BUS_OFF) { |
|
/* Bus off */ |
|
CO_errorReport(em, CO_EM_CAN_TX_BUS_OFF, |
|
CO_EMC_BUS_OFF_RECOVERED, errors); |
|
} else { |
|
/* Bus not off */ |
|
CO_errorReset(em, CO_EM_CAN_TX_BUS_OFF, errors); |
|
|
|
if ((err_cnt.rx_err_cnt >= 96U) || |
|
(err_cnt.tx_err_cnt >= 96U)) { |
|
/* Bus warning */ |
|
CO_errorReport(em, CO_EM_CAN_BUS_WARNING, |
|
CO_EMC_NO_ERROR, errors); |
|
} else { |
|
/* Bus not warning */ |
|
CO_errorReset(em, CO_EM_CAN_BUS_WARNING, |
|
errors); |
|
} |
|
|
|
if (err_cnt.rx_err_cnt >= 128U) { |
|
/* Bus rx passive */ |
|
CO_errorReport(em, CO_EM_CAN_RX_BUS_PASSIVE, |
|
CO_EMC_CAN_PASSIVE, errors); |
|
} else { |
|
/* Bus not rx passive */ |
|
CO_errorReset(em, CO_EM_CAN_RX_BUS_PASSIVE, |
|
errors); |
|
} |
|
|
|
if (err_cnt.tx_err_cnt >= 128U && |
|
!CANmodule->first_tx_msg) { |
|
/* Bus tx passive */ |
|
CO_errorReport(em, CO_EM_CAN_TX_BUS_PASSIVE, |
|
CO_EMC_CAN_PASSIVE, errors); |
|
} else if (CO_isError(em, CO_EM_CAN_TX_BUS_PASSIVE)) { |
|
/* Bus not tx passive */ |
|
CO_errorReset(em, CO_EM_CAN_TX_BUS_PASSIVE, |
|
errors); |
|
CO_errorReset(em, CO_EM_CAN_TX_OVERFLOW, |
|
errors); |
|
} |
|
} |
|
|
|
/* This code can be activated if we can read the overflows*/ |
|
if (false && rx_overflows != 0U) { |
|
CO_errorReport(em, CO_EM_CAN_RXB_OVERFLOW, |
|
CO_EMC_CAN_OVERRUN, errors); |
|
} |
|
} |
|
} |
|
|
|
static int canopen_init(void) |
|
{ |
|
|
|
k_work_queue_start(&canopen_tx_workq, canopen_tx_workq_stack, |
|
K_KERNEL_STACK_SIZEOF(canopen_tx_workq_stack), |
|
CONFIG_CANOPENNODE_TX_WORKQUEUE_PRIORITY, NULL); |
|
|
|
k_thread_name_set(&canopen_tx_workq.thread, "canopen_tx_workq"); |
|
|
|
k_work_init(&canopen_tx_queue.work, canopen_tx_retry); |
|
|
|
return 0; |
|
} |
|
|
|
SYS_INIT(canopen_init, APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
|
|
|