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:
parent
ea355a0032
commit
e8df8a5790
1 changed files with 80 additions and 83 deletions
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue