mirror of
https://github.com/apache/nuttx.git
synced 2026-09-03 23:52:58 +00:00
arch/arm/src/imxrt: Unify FlexCAN TX work and add SIOCGCANERRORS.
TX-complete and the deadline watchdog each queued their own callback on the same work_s, and work_queue() cancels whatever is pending when a work_s is reused, so whichever ran second was dropped: deadlines were left set, the TX interrupt mask stayed off, or expired frames were never aborted. Both now queue imxrt_tx_work(), which retires completions before it aborts expired mailboxes. Add SIOCGCANERRORS so a socket can read fault confinement, TEC/REC, a monotonic bus error count and the RX mailbox overrun count. SIOCGCANSTATE reports sleep/operational, not fault confinement, hence a new command. The error count is sampled from the clear-on-read ESR1 error flags at every driver entry rather than from ERRINT, which fires per error frame and storms at bus rate once the bus is dead. Frames the CAN socket layer drops for want of an IOB now count as rx_dropped in the netdev statistics as well as in the global CAN statistics. Tested on an i.MX RT1176 (ARK FMU-v6XRT) running PX4 with two DroneCAN nodes: unplugging one node the ioctl reports error-passive, TEC 128, REC 0 and a monotonic error count, matching ECR/ESR1 read over SWD at 20 Hz, while the other interface stays error-active with zero errors. Assisted-by: Claude:claude-fable-5 Signed-off-by: Jacob Dahl <dahl.jakejacob@gmail.com>
This commit is contained in:
parent
60ef78f042
commit
eb40eed248
7 changed files with 172 additions and 34 deletions
|
|
@ -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. */
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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 ***************************************************/
|
||||
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue