diff --git a/arch/arm/src/imxrt/hardware/imxrt_flexcan.h b/arch/arm/src/imxrt/hardware/imxrt_flexcan.h index d55ac68ee1c..a1e7df4c594 100644 --- a/arch/arm/src/imxrt/hardware/imxrt_flexcan.h +++ b/arch/arm/src/imxrt/hardware/imxrt_flexcan.h @@ -435,6 +435,13 @@ #define CAN_FDCRC_FD_MBCRC(x) (((uint32_t)(((uint32_t)(x)) << CAN_FDCRC_FD_MBCRC_SHIFT)) & CAN_FDCRC_FD_MBCRC_MASK) /* Bit 31: Reserved */ +/* CAN MB RX codes */ + +#define CAN_RXMB_INACTIVE 0x0 /* MB is not active. */ +#define CAN_RXMB_FULL 0x2 /* MB is full. */ +#define CAN_RXMB_EMPTY 0x4 /* MB is empty. */ +#define CAN_RXMB_OVERRUN 0x6 /* MB is overwritten into a full buffer. */ + /* CAN MB TX codes */ #define CAN_TXMB_INACTIVE 0x8 /* MB is not active. */ #define CAN_TXMB_ABORT 0x9 /* MB is aborted. */ diff --git a/arch/arm/src/imxrt/imxrt_flexcan.c b/arch/arm/src/imxrt/imxrt_flexcan.c index 598f438dabf..d1e8f4ef149 100644 --- a/arch/arm/src/imxrt/imxrt_flexcan.c +++ b/arch/arm/src/imxrt/imxrt_flexcan.c @@ -298,6 +298,9 @@ struct imxrt_driver_s #ifdef CONFIG_NET_CAN_RAW_TX_DEADLINE struct txmbstats txmb[TXMBRINGSIZE]; #endif + + uint32_t bus_errors; /* ESR1 error flags observed, monotonic */ + uint32_t rx_overruns; /* RX mailbox CODE=OVERRUN count */ }; /**************************************************************************** @@ -488,7 +491,7 @@ static struct mb_s *flexcan_get_mb(struct imxrt_driver_s *priv, static void imxrt_receive(struct imxrt_driver_s *priv, uint32_t flags); -static void imxrt_txdone_work(void *arg); +static void imxrt_tx_work(void *arg); static void imxrt_txdone(struct imxrt_driver_s *priv); static int imxrt_flexcan_interrupt(int irq, void *context, @@ -497,7 +500,7 @@ static void imxrt_flexcan_interrupt_work(void *arg); /* Watchdog timer expirations */ #ifdef TX_TIMEOUT_WQ -static void imxrt_txtimeout_work(void *arg); +static void imxrt_txtimeout_abort(struct imxrt_driver_s *priv); static void imxrt_txtimeout_expiry(wdparm_t arg); #endif @@ -523,6 +526,54 @@ static void imxrt_reset(struct imxrt_driver_s *priv); * Private Functions ****************************************************************************/ +/* ESR1 error flags. Every one is set by the protocol engine on the matching + * bus error and cleared when ESR1 is read, so a read reports the error + * types seen since the previous read. Sampling them at every driver entry + * gives a monotonic error count without the ERRINT interrupt, which fires + * once per error frame and storms at bus rate on a dead bus. + */ + +#define ESR1_ERRFLAGS (CAN_ESR1_STFERR | CAN_ESR1_FRMERR | CAN_ESR1_CRCERR | \ + CAN_ESR1_ACKERR | CAN_ESR1_BIT0ERR | CAN_ESR1_BIT1ERR | \ + CAN_ESR1_STFERRFAST | CAN_ESR1_FRMERRFAST | \ + CAN_ESR1_CRCERRFAST | CAN_ESR1_BIT0ERRFAST | \ + CAN_ESR1_BIT1ERRFAST) + +/**************************************************************************** + * Function: imxrt_sample_errors + * + * Description: + * Read ESR1, clearing its error flags, and add the number of set flags + * to the bus error count. + * + * Input Parameters: + * priv - Reference to the driver state structure + * + * Returned Value: + * The ESR1 value that was read. + * + ****************************************************************************/ + +static uint32_t imxrt_sample_errors(struct imxrt_driver_s *priv) +{ + irqstate_t flags; + uint32_t esr1; + uint32_t errs; + + flags = spin_lock_irqsave(NULL); + + esr1 = getreg32(priv->base + IMXRT_CAN_ESR1_OFFSET); + + for (errs = esr1 & ESR1_ERRFLAGS; errs != 0; errs &= errs - 1) + { + priv->bus_errors++; + } + + spin_unlock_irqrestore(NULL, flags); + + return esr1; +} + /**************************************************************************** * Function: imxrt_txmb_next * @@ -895,6 +946,13 @@ static void imxrt_receive(struct imxrt_driver_s *priv, rf = flexcan_get_mb(priv, mbi); + /* CODE is in CS; read it before unlocking the mailbox via IFLAG. */ + + if (rf->cs.code == CAN_RXMB_OVERRUN) + { + priv->rx_overruns++; + } + /* Read the frame contents */ #ifdef CONFIG_NET_CAN_CANFD @@ -1067,7 +1125,7 @@ static void imxrt_txdone(struct imxrt_driver_s *priv) /* Retire the deadline with the frame. Left behind it sits in the * past forever, and the next expiry of any other mailbox's - * watchdog makes imxrt_txtimeout_work() abort whatever frame has + * watchdog makes imxrt_txtimeout_abort() abort whatever frame has * since been loaded here. */ @@ -1089,35 +1147,48 @@ static void imxrt_txdone(struct imxrt_driver_s *priv) } /**************************************************************************** - * Function: imxrt_txdone_work + * Function: imxrt_tx_work * * Description: - * An interrupt was received indicating that the last TX packet(s) is done + * Process TX completions and deadline aborts on the worker thread, then + * poll for more data. TX-complete IRQs and the deadline watchdog both + * queue this function on the same work_s; work_queue() cancels a pending + * callback when that work_s is reused, so splitting them lost whichever + * ran second (deadlines left set, TX IMASK left off, expired frames + * never aborted). * * Input Parameters: - * priv - Reference to the driver state structure + * arg - Reference to the driver state structure * * Returned Value: * None * - * Assumptions: - * Global interrupts are disabled by the watchdog logic. - * We are not in an interrupt context so that we can lock the network. - * ****************************************************************************/ -static void imxrt_txdone_work(void *arg) +static void imxrt_tx_work(void *arg) { struct imxrt_driver_s *priv = (struct imxrt_driver_s *)arg; + imxrt_sample_errors(priv); imxrt_txdone(priv); - /* There should be space for a new TX in any event. Poll the network for - * new XMIT data +#ifdef TX_TIMEOUT_WQ + imxrt_txtimeout_abort(priv); +#endif + + /* The TX IRQ masked every TX mailbox to stop the interrupt storm. + * Restore the mask so abort completions and later transmits can + * interrupt. txdone() already cleared completion flags. */ + modifyreg32(priv->base + IMXRT_CAN_IMASK1_OFFSET, 0, IFLAG1_TX); + net_lock(); - devif_poll(&priv->dev, imxrt_txpoll); + if (priv->bifup) + { + devif_poll(&priv->dev, imxrt_txpoll); + } + net_unlock(); } @@ -1150,6 +1221,8 @@ static void imxrt_flexcan_interrupt_work(void *arg) flags = getreg32(priv->base + IMXRT_CAN_IFLAG1_OFFSET); flags &= IFLAG1_RX; + imxrt_sample_errors(priv); + net_lock(); imxrt_receive(priv, flags); net_unlock(); @@ -1208,7 +1281,7 @@ static int imxrt_flexcan_interrupt(int irq, void *context, */ modifyreg32(priv->base + IMXRT_CAN_IMASK1_OFFSET, IFLAG1_TX, 0); - work_queue(CANWORK, &priv->irqwork, imxrt_txdone_work, priv, 0); + work_queue(CANWORK, &priv->irqwork, imxrt_tx_work, priv, 0); } } @@ -1216,25 +1289,24 @@ static int imxrt_flexcan_interrupt(int irq, void *context, } /**************************************************************************** - * Function: imxrt_txtimeout_work + * Function: imxrt_txtimeout_abort * * Description: - * Perform TX timeout related work from the worker thread + * Abort TX mailboxes whose deadline has passed. Called from + * imxrt_tx_work() after completions have been retired so a just-finished + * mailbox is not aborted on a stale deadline. * * Input Parameters: - * arg - The argument passed when work_queue() as called. + * priv - Reference to the driver state structure * * Returned Value: - * OK on success - * - * Assumptions: + * None * ****************************************************************************/ #ifdef TX_TIMEOUT_WQ -static void imxrt_txtimeout_work(void *arg) +static void imxrt_txtimeout_abort(struct imxrt_driver_s *priv) { - struct imxrt_driver_s *priv = (struct imxrt_driver_s *)arg; uint32_t flags; uint32_t mbi; uint32_t mb_bit; @@ -1246,10 +1318,6 @@ static void imxrt_txtimeout_work(void *arg) now.tv_sec = ts.tv_sec; now.tv_usec = ts.tv_nsec / 1000; - /* The watchdog timed out, yet we still check mailboxes in case the - * transmit function transmitted a new frame - */ - flags = getreg32(priv->base + IMXRT_CAN_IFLAG1_OFFSET); for (mbi = 0; mbi < TXMBRINGSIZE; mbi++) @@ -1288,7 +1356,6 @@ static void imxrt_txtimeout_work(void *arg) mb = flexcan_get_mb(priv, RXMBCOUNT + 1 + mbi); mb->cs.code = CAN_TXMB_ABORT; - priv->txmb[mbi].pending = TX_ABORT; } } @@ -1297,7 +1364,8 @@ static void imxrt_txtimeout_work(void *arg) * * Description: * Our TX watchdog timed out. Called from the timer interrupt handler. - * The last TX never completed. Reset the hardware and start again. + * Queue the same TX worker as the completion IRQ so the two cannot + * cancel each other. * * Input Parameters: * arg - The argument @@ -1314,10 +1382,7 @@ static void imxrt_txtimeout_expiry(wdparm_t arg) { struct imxrt_driver_s *priv = (struct imxrt_driver_s *)arg; - /* Schedule to perform the TX timeout processing on the worker thread - */ - - work_queue(CANWORK, &priv->irqwork, imxrt_txtimeout_work, priv, 0); + work_queue(CANWORK, &priv->irqwork, imxrt_tx_work, priv, 0); } #endif @@ -1428,6 +1493,8 @@ static int imxrt_ifup(struct net_driver_s *dev) } priv->bifup = true; + priv->bus_errors = 0; + priv->rx_overruns = 0; priv->txdesc = (struct can_frame *)&g_tx_pool; priv->rxdesc = (struct can_frame *)&g_rx_pool; if (priv->canfd_capable) @@ -1660,7 +1727,44 @@ static int imxrt_ioctl(struct net_driver_s *dev, int cmd, } } break; + + case SIOCGCANERRORS: + { + struct can_ioctl_errors_s *req = + (struct can_ioctl_errors_s *)((uintptr_t)arg); + uint32_t esr1 = imxrt_sample_errors(priv); + uint32_t ecr = getreg32(priv->base + IMXRT_CAN_ECR_OFFSET); + uint32_t flt = (esr1 & CAN_ESR1_FLTCONF_MASK) >> + CAN_ESR1_FLTCONF_SHIFT; + + if (flt >= 2) + { + req->state = CAN_ERRSTATE_BUSOFF; + } + else if (flt == 1) + { + req->state = CAN_ERRSTATE_PASSIVE; + } + else if (esr1 & (CAN_ESR1_TXWRN | CAN_ESR1_RXWRN)) + { + req->state = CAN_ERRSTATE_WARNING; + } + else + { + req->state = CAN_ERRSTATE_ACTIVE; + } + + req->txerr = (ecr & CAN_ECR_TXERRCNT_MASK) >> + CAN_ECR_TXERRCNT_SHIFT; + req->rxerr = (ecr & CAN_ECR_RXERRCNT_MASK) >> + CAN_ECR_RXERRCNT_SHIFT; + req->errors = priv->bus_errors; + req->rx_overruns = priv->rx_overruns; + ret = OK; + } + break; #endif + default: ret = -ENOTTY; break; diff --git a/include/net/if.h b/include/net/if.h index db49af71b25..3f7174e46b9 100644 --- a/include/net/if.h +++ b/include/net/if.h @@ -237,6 +237,25 @@ struct can_ioctl_state_s enum can_ioctl_state_e state; }; +/* Fault confinement and error counters, SIOCGCANERRORS ioctl command. + * WARNING is error-active with either error counter at or above 96; + * PASSIVE and BUSOFF follow the controller's own state. + */ + +#define CAN_ERRSTATE_ACTIVE 0 +#define CAN_ERRSTATE_WARNING 1 +#define CAN_ERRSTATE_PASSIVE 2 +#define CAN_ERRSTATE_BUSOFF 3 + +struct can_ioctl_errors_s +{ + uint8_t state; /* CAN_ERRSTATE_* */ + uint8_t txerr; /* Transmit error counter (TEC) */ + uint8_t rxerr; /* Receive error counter (REC) */ + uint32_t errors; /* Bus errors observed since ifup, monotonic */ + uint32_t rx_overruns; /* RX frames the controller overwrote unread */ +}; + /* There are two forms of the I/F request structure. * One for IPv6 and one for IPv4. * Notice that they are (and must be) cast compatible and really different @@ -264,6 +283,7 @@ struct lifreq struct can_ioctl_data_s lifru_can_data; /* CAN bitrate request data */ struct can_ioctl_filter_s lifru_can_filter; /* CAN filter request data */ struct can_ioctl_state_s lifru_can_state; /* CAN/LIN controller state */ + struct can_ioctl_errors_s lifru_can_errors; /* CAN error counters */ } lifr_ifru; }; @@ -322,6 +342,7 @@ struct ifreq struct can_ioctl_data_s ifru_can_data; /* CAN bitrate request data */ struct can_ioctl_filter_s ifru_can_filter; /* CAN filter request data */ struct can_ioctl_state_s ifru_can_state; /* CAN/LIN controller state */ + struct can_ioctl_errors_s ifru_can_errors; /* CAN error counters */ FAR void *ifru_data; /* For use by interface */ } ifr_ifru; }; diff --git a/include/nuttx/net/ioctl.h b/include/nuttx/net/ioctl.h index 75bf689f9d5..b1519dddff5 100644 --- a/include/nuttx/net/ioctl.h +++ b/include/nuttx/net/ioctl.h @@ -127,6 +127,8 @@ #define SIOCCANRECOVERY _SIOC(0x0032) /* Recovery can, work only when bus-off state */ #define SIOCGCANSTATE _SIOC(0x0041) /* Get state from a CAN/LIN controller */ #define SIOCSCANSTATE _SIOC(0x0042) /* Set the LIN/CAN controller state */ +#define SIOCGCANERRORS _SIOC(0x0045) /* Get CAN error counters and fault + * confinement state */ /* Network socket control ***************************************************/ diff --git a/net/can/can_callback.c b/net/can/can_callback.c index 323e93893a0..667f1061496 100644 --- a/net/can/can_callback.c +++ b/net/can/can_callback.c @@ -92,6 +92,7 @@ can_data_event(FAR struct net_driver_s *dev, FAR struct can_conn_s *conn, #ifdef CONFIG_NET_STATISTICS g_netstats.can.drop++; #endif + NETDEV_RXDROPPED(dev); } /* In any event, the new data has now been handled */ diff --git a/net/netdev/Kconfig b/net/netdev/Kconfig index 5570cd16027..a040c471c2c 100644 --- a/net/netdev/Kconfig +++ b/net/netdev/Kconfig @@ -23,7 +23,8 @@ config NETDEV_CAN_IOCTL depends on NET_CAN ---help--- Enable support for the CAN network device ioctl() commands: - bitrate, hardware-level filters and CAN/LIN controller state + bitrate, hardware-level filters, CAN/LIN controller state and + error counters (NOTE: Not every command is supported by every driver) config NETDEV_WIRELESS_IOCTL diff --git a/net/netdev/netdev_ioctl.c b/net/netdev/netdev_ioctl.c index e49e449390c..64b6a04f3cd 100644 --- a/net/netdev/netdev_ioctl.c +++ b/net/netdev/netdev_ioctl.c @@ -775,6 +775,7 @@ static ssize_t net_ioctl_ifreq_arglen(uint8_t domain, int cmd) case SIOCCANRECOVERY: case SIOCGCANSTATE: case SIOCSCANSTATE: + case SIOCGCANERRORS: case SIOCSIFNAME: case SIOCGIFNAME: case SIOCGIFINDEX: @@ -1264,6 +1265,7 @@ static int netdev_ifr_ioctl(FAR struct socket *psock, int cmd, case SIOCCANRECOVERY: /* Recovery can controller when bus-off */ case SIOCGCANSTATE: /* Get state from a CAN/LIN controller */ case SIOCSCANSTATE: /* Set the LIN/CAN controller state */ + case SIOCGCANERRORS: /* Get CAN error counters and state */ if (dev->d_ioctl) { /* Every CAN ioctl argument struct is a member of the