drivers: can: mcux: flexcan: rework transmit error handling

Rework the transmit error handling in the NXP MCUX FlexCAN driver:
- Frame transmission must be automatically retried in case of lost
  arbitration or missing acknowledge.
- Abort any pending TX frames when bus-off state is entered.
- Fail early in can_send() if in bus-off state.

Fixes: #19502

Signed-off-by: Henrik Brix Andersen <hebad@vestas.com>
This commit is contained in:
Henrik Brix Andersen 2022-01-10 14:53:16 +01:00 committed by Anas Nashif
commit e8df8a5790

View file

@ -316,6 +316,30 @@ static int mcux_get_tx_alloc(struct mcux_flexcan_data *data)
return alloc >= MCUX_FLEXCAN_MAX_TX ? -1 : alloc;
}
static enum can_state mcux_flexcan_get_state(const struct device *dev,
struct can_bus_err_cnt *err_cnt)
{
const struct mcux_flexcan_config *config = dev->config;
uint64_t status_flags;
if (err_cnt != NULL) {
FLEXCAN_GetBusErrCount(config->base, &err_cnt->tx_err_cnt,
&err_cnt->rx_err_cnt);
}
status_flags = FLEXCAN_GetStatusFlags(config->base);
if ((status_flags & CAN_ESR1_FLTCONF(2)) != 0U) {
return CAN_BUS_OFF;
}
if ((status_flags & CAN_ESR1_FLTCONF(1)) != 0U) {
return CAN_ERROR_PASSIVE;
}
return CAN_ERROR_ACTIVE;
}
static int mcux_flexcan_send(const struct device *dev,
const struct zcan_frame *frame,
k_timeout_t timeout,
@ -332,6 +356,11 @@ static int mcux_flexcan_send(const struct device *dev,
return -EINVAL;
}
if (mcux_flexcan_get_state(dev, NULL) == CAN_BUS_OFF) {
LOG_DBG("Transmit failed, bus-off");
return -ENETDOWN;
}
while (true) {
alloc = mcux_get_tx_alloc(data);
if (alloc >= 0) {
@ -433,31 +462,6 @@ static void mcux_flexcan_set_state_change_callback(const struct device *dev,
data->state_change_cb_data = user_data;
}
static enum can_state mcux_flexcan_get_state(const struct device *dev,
struct can_bus_err_cnt *err_cnt)
{
const struct mcux_flexcan_config *config = dev->config;
uint32_t status_flags;
if (err_cnt) {
FLEXCAN_GetBusErrCount(config->base, &err_cnt->tx_err_cnt,
&err_cnt->rx_err_cnt);
}
status_flags = (FLEXCAN_GetStatusFlags(config->base) &
CAN_ESR1_FLTCONF_MASK) << CAN_ESR1_FLTCONF_SHIFT;
if (status_flags & 0x02) {
return CAN_BUS_OFF;
}
if (status_flags & 0x01) {
return CAN_ERROR_PASSIVE;
}
return CAN_ERROR_ACTIVE;
}
#ifndef CONFIG_CAN_AUTO_BUS_OFF_RECOVERY
int mcux_flexcan_recover(const struct device *dev, k_timeout_t timeout)
{
@ -522,76 +526,60 @@ static inline void mcux_flexcan_transfer_error_status(const struct device *dev,
struct mcux_flexcan_data *data = dev->data;
const can_state_change_callback_t cb = data->state_change_cb;
void *cb_data = data->state_change_cb_data;
can_tx_callback_t function;
int status = 0;
void *arg;
int alloc;
enum can_state state;
struct can_bus_err_cnt err_cnt;
if (error & CAN_ESR1_FLTCONF(2)) {
LOG_DBG("Tx bus off (error 0x%08llx)", error);
status = -ENETDOWN;
} else if ((error & kFLEXCAN_Bit0Error) ||
(error & kFLEXCAN_Bit1Error)) {
LOG_DBG("TX arbitration lost (error 0x%08llx)", error);
status = -EBUSY;
} else if (error & kFLEXCAN_AckError) {
LOG_DBG("TX no ACK received (error 0x%08llx)", error);
status = -EIO;
} else if (error & kFLEXCAN_StuffingError) {
LOG_DBG("RX stuffing error (error 0x%08llx)", error);
} else if (error & kFLEXCAN_FormError) {
LOG_DBG("RX form error (error 0x%08llx)", error);
} else if (error & kFLEXCAN_CrcError) {
LOG_DBG("RX CRC error (error 0x%08llx)", error);
} else {
LOG_DBG("Unhandled error (error 0x%08llx)", error);
if ((error & (kFLEXCAN_Bit0Error & kFLEXCAN_Bit1Error)) != 0U) {
LOG_DBG("TX arbitration lost (error 0x%016llx)", error);
}
if ((error & kFLEXCAN_AckError) != 0U) {
LOG_DBG("TX no ACK received (error 0x%016llx)", error);
}
if ((error & kFLEXCAN_StuffingError) != 0U) {
LOG_DBG("RX stuffing error (error 0x%016llx)", error);
}
if ((error & kFLEXCAN_FormError) != 0U) {
LOG_DBG("RX form error (error 0x%016llx)", error);
}
if ((error & kFLEXCAN_CrcError) != 0U) {
LOG_DBG("RX CRC error (error 0x%016llx)", error);
}
state = mcux_flexcan_get_state(dev, &err_cnt);
if (data->state != state) {
data->state = state;
if (cb) {
if (cb != NULL) {
cb(state, err_cnt, cb_data);
}
}
if (status == 0) {
/*
* Error/status is not TX related. No further action
* required.
*/
return;
}
if (state == CAN_BUS_OFF) {
/* Abort any pending TX frames in case of bus-off */
for (alloc = 0; alloc < MCUX_FLEXCAN_MAX_TX; alloc++) {
/* Copy callback function and argument before clearing bit */
function = data->tx_cbs[alloc].function;
arg = data->tx_cbs[alloc].arg;
/*
* Since the FlexCAN module ESR1 register accumulates errors
* and warnings across multiple transmitted frames (until the
* CPU reads the register) it is not possible to find out
* which transfer caused the error/warning.
*
* We therefore propagate the error/warning to all currently
* active transmitters.
*/
for (alloc = 0; alloc < MCUX_FLEXCAN_MAX_TX; alloc++) {
/* Copy callback function and argument before clearing bit */
function = data->tx_cbs[alloc].function;
arg = data->tx_cbs[alloc].arg;
if (atomic_test_and_clear_bit(data->tx_allocs, alloc)) {
FLEXCAN_TransferAbortSend(config->base, &data->handle,
ALLOC_IDX_TO_TXMB_IDX(alloc));
if (function != NULL) {
function(-ENETDOWN, arg);
} else {
data->tx_cbs[alloc].status = -ENETDOWN;
k_sem_give(&data->tx_cbs[alloc].done);
}
if (atomic_test_and_clear_bit(data->tx_allocs, alloc)) {
FLEXCAN_TransferAbortSend(config->base, &data->handle,
ALLOC_IDX_TO_TXMB_IDX(alloc));
if (function != NULL) {
function(status, arg);
} else {
data->tx_cbs[alloc].status = status;
k_sem_give(&data->tx_cbs[alloc].done);
k_sem_give(&data->tx_allocs_sem);
}
k_sem_give(&data->tx_allocs_sem);
}
}
}
@ -660,28 +648,37 @@ static inline void mcux_flexcan_transfer_rx_idle(const struct device *dev,
static FLEXCAN_CALLBACK(mcux_flexcan_transfer_callback)
{
struct mcux_flexcan_data *data = (struct mcux_flexcan_data *)userData;
/*
* The result field can either be a MB index (which is limited to 32 bit
* value) or a status flags value, which is 32 bit on some platforms but
* 64 on others. To decouple the remaining functions from this, the
* result field is always promoted to uint64_t.
*/
uint32_t mb = (uint32_t)result;
uint64_t status_flags = result;
ARG_UNUSED(base);
switch (status) {
case kStatus_FLEXCAN_UnHandled:
/* Not all fault confinement state changes are handled by the HAL */
__fallthrough;
case kStatus_FLEXCAN_ErrorStatus:
mcux_flexcan_transfer_error_status(data->dev, (uint64_t)result);
mcux_flexcan_transfer_error_status(data->dev, status_flags);
break;
case kStatus_FLEXCAN_TxSwitchToRx:
__fallthrough;
case kStatus_FLEXCAN_TxIdle:
/* The result field is a MB value which is limited to 32bit value */
mcux_flexcan_transfer_tx_idle(data->dev, (uint32_t)result);
mcux_flexcan_transfer_tx_idle(data->dev, mb);
break;
case kStatus_FLEXCAN_RxOverflow:
__fallthrough;
case kStatus_FLEXCAN_RxIdle:
/* The result field is a MB value which is limited to 32bit value */
mcux_flexcan_transfer_rx_idle(data->dev, (uint32_t)result);
mcux_flexcan_transfer_rx_idle(data->dev, mb);
break;
default:
LOG_WRN("Unhandled error/status (status 0x%08x, "
"result = 0x%08llx", status, (uint64_t)result);
LOG_WRN("Unhandled status 0x%08x (result = 0x%016llx)",
status, status_flags);
}
}