| /* |
| * 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); |