This is an automated email from the ASF dual-hosted git repository. acassis pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/nuttx.git
commit 590fa91442c14c45605dbe960b7142d5a8d868da Author: Darryl Ring <[email protected]> AuthorDate: Thu Oct 22 11:10:10 2026 -0700 arch/arm/stm32h5: Add NART (no automatic retry) Add support for the CANIOC_SET_NART ioctl which controls whether or not the FDCAN peripheral automatically resends frames that lose arbitration or hit bus errors. Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Darryl Ring <[email protected]> --- arch/arm/src/stm32h5/stm32_fdcan.c | 189 ++++++++++++++++++++++++++++--------- 1 file changed, 145 insertions(+), 44 deletions(-) diff --git a/arch/arm/src/stm32h5/stm32_fdcan.c b/arch/arm/src/stm32h5/stm32_fdcan.c index 1be0ff65cbc..65d0a81b512 100644 --- a/arch/arm/src/stm32h5/stm32_fdcan.c +++ b/arch/arm/src/stm32h5/stm32_fdcan.c @@ -427,6 +427,8 @@ struct stm32_fdcan_s uint8_t nstdalloc; /* Number of allocated standard filters */ uint32_t nbtp; /* Current nominal bit timing */ uint32_t dbtp; /* Current data bit timing */ + bool dar; /* Automatic retransmission disabled */ + uint32_t txbusy; /* TX buffers requested and not yet reaped */ uint32_t rxints; /* Configured RX interrupts */ uint32_t txints; /* Configured TX interrupts */ @@ -513,6 +515,7 @@ static void fdcan_error(struct can_dev_s *dev, uint32_t status); static void fdcan_receive(struct can_dev_s *dev, volatile uint32_t *rxbuffer, unsigned long nwords); +static void fdcan_reaptx(struct can_dev_s *dev); static int fdcan_interrupt(int irq, void *context, void *arg); /* Hardware initialization */ @@ -2163,6 +2166,68 @@ static int fdcan_ioctl(struct can_dev_s *dev, int cmd, unsigned long arg) } break; + /* CANIOC_SET_NART: + * Description : Enable/Disable NART (No Automatic Retry). The frame + * is dropped instead of retried if it loses + * arbitration or hits an error (CCCR.DAR). + * Argument : 1 to disable automatic retransmission, 0 to enable it + * Returned Value : Zero (OK) is returned on success. Otherwise -1 + * (ERROR) is returned with the errno variable set + * to indicate the nature of the error. + * Dependencies : None + * + * CCCR.DAR is protected, so the controller passes through + * initialization mode, which drops any frame on the wire. Call this + * while the bus is quiet. + */ + + case CANIOC_SET_NART: + { + irqstate_t flags; + uint32_t regval; + bool dar = (arg != 0); + + if (priv->dar == dar) + { + ret = OK; + break; + } + + flags = enter_critical_section(); + + priv->dar = dar; + + regval = fdcan_getreg(priv, STM32_FDCAN_CCCR_OFFSET); + fdcan_putreg(priv, STM32_FDCAN_CCCR_OFFSET, + regval | FDCAN_CCCR_INIT); + while ((fdcan_getreg(priv, STM32_FDCAN_CCCR_OFFSET) & + FDCAN_CCCR_INIT) == 0); + + regval = fdcan_getreg(priv, STM32_FDCAN_CCCR_OFFSET); + fdcan_putreg(priv, STM32_FDCAN_CCCR_OFFSET, + regval | FDCAN_CCCR_CCE); + + regval = fdcan_getreg(priv, STM32_FDCAN_CCCR_OFFSET) & + ~FDCAN_CCCR_DAR; + if (dar) + { + regval |= FDCAN_CCCR_DAR; + } + + fdcan_putreg(priv, STM32_FDCAN_CCCR_OFFSET, regval); + + regval &= ~(FDCAN_CCCR_INIT | FDCAN_CCCR_CCE); + fdcan_putreg(priv, STM32_FDCAN_CCCR_OFFSET, regval); + + leave_critical_section(flags); + + /* Entering initialization mode cancels any pending transmission */ + + fdcan_reaptx(dev); + ret = OK; + } + break; + /* Unsupported/unrecognized command */ default: @@ -2342,14 +2407,19 @@ static int fdcan_send(struct can_dev_s *dev, struct can_msg_s *msg) *dest++ = wordbuffer; } - /* Enable transmit interrupts from the TX FIFOQ buffer by setting TC - * interrupt bit in IR (also requires that the TC interrupt is enabled) + /* Enable transmit interrupts from every TX FIFOQ buffer by setting their + * TC interrupt bits (also requires that the TC interrupt is enabled), so + * each buffer is reaped as soon as it finishes. fdcan_txready() holds + * a buffer back until then. */ - fdcan_putreg(priv, STM32_FDCAN_TXBTIE_OFFSET, (1 << ndx)); + fdcan_putreg(priv, STM32_FDCAN_TXBTIE_OFFSET, + (1 << config->ntxfifoq) - 1); /* And request to send the packet */ + DEBUGASSERT((priv->txbusy & (1 << ndx)) == 0); + priv->txbusy |= (1 << ndx); fdcan_putreg(priv, STM32_FDCAN_TXBAR_OFFSET, (1 << ndx)); return OK; @@ -2373,16 +2443,20 @@ static bool fdcan_txready(struct can_dev_s *dev) { struct stm32_fdcan_s *priv = dev->cd_priv; uint32_t regval = 0; + uint32_t ndx = 0; bool notfull = false; /* Return the state of the TX FIFOQ. Return TRUE if the TX FIFO/Queue is - * not full. + * not full and the buffer it would use next has been reaped: the + * hardware frees a buffer before its interrupt reports it done, and + * reusing it then would lose that completion. */ regval = fdcan_getreg(priv, STM32_FDCAN_TXFQS_OFFSET); notfull = ((regval & FDCAN_TXFQS_TFQF) == 0); + ndx = (regval & FDCAN_TXFQS_TFQPI_MASK) >> FDCAN_TXFQS_TFQPI_SHIFT; - return notfull; + return notfull && (priv->txbusy & (1 << ndx)) == 0; } /**************************************************************************** @@ -2405,35 +2479,17 @@ static bool fdcan_txready(struct can_dev_s *dev) static bool fdcan_txempty(struct can_dev_s *dev) { - struct stm32_fdcan_s *priv = dev->cd_priv; - uint32_t regval = 0; -#ifndef CONFIG_STM32_FDCAN_QUEUE_MODE - int tffl = 0; - bool empty = false; -#endif + struct stm32_fdcan_s *priv = dev->cd_priv; DEBUGASSERT(priv != NULL && priv->config != NULL); - /* Return the state of the TX FIFOQ. Return TRUE if the TX FIFO/Queue is - * empty. We don't have a reliable indication that the FIFO is empty, so - * we have to use some heuristics. + /* Empty only once every request has been reported done. The upper half + * retires a message itself when its queue is full and this says empty, + * so saying empty while a completion is still owed would report that + * message done twice. */ - regval = fdcan_getreg(priv, STM32_FDCAN_TXFQS_OFFSET); - if ((regval & FDCAN_TXFQS_TFQF) != 0) - { - return false; - } - - /* Tx FIFO Free Level */ - -#ifndef CONFIG_STM32_FDCAN_QUEUE_MODE - tffl = (regval & FDCAN_TXFQS_TFFL_MASK) >> FDCAN_TXFQS_TFFL_SHIFT; - empty = (tffl >= priv->config->ntxfifoq); - return empty; -#else - return true; -#endif + return priv->txbusy == 0; } /**************************************************************************** @@ -2791,6 +2847,42 @@ static void fdcan_receive(struct can_dev_s *dev, } } +/**************************************************************************** + * Name: fdcan_reaptx + * + * Description: + * Call can_txdone() once for each TX buffer we requested that is no + * longer pending, whether it was sent or dropped. TXBTO cannot be used + * for this: its bits stay set until the buffer is requested again, and + * are never set for a frame dropped with retransmission disabled. + * + * Input Parameters: + * dev - CAN-common state data + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void fdcan_reaptx(struct can_dev_s *dev) +{ + struct stm32_fdcan_s *priv = dev->cd_priv; + irqstate_t flags; + uint32_t done; + + flags = enter_critical_section(); + + done = priv->txbusy & ~fdcan_getreg(priv, STM32_FDCAN_TXBRP_OFFSET); + priv->txbusy &= ~done; + + for (; done != 0; done &= done - 1) + { + can_txdone(dev); + } + + leave_critical_section(flags); +} + /**************************************************************************** * Name: fdcan_interrupt * @@ -2921,16 +3013,26 @@ static int fdcan_interrupt(int irq, void *context, void *arg) #endif } - /* Check for successful completion of a transmission */ + /* Check for completion of a transmission. + * + * A frame dropped with automatic retransmission disabled (lost + * arbitration or an error) raises neither TC nor TCF, though its TXBCF + * bit is set and TXBCIE enables it (measured on the STM32H563). It does + * empty the TX FIFO, and with retransmission disabled every pending frame + * finishes within a frame time, so the FIFO always empties: TFE always + * comes. Without it, a node that only ever loses arbitration never gets + * its sender slots back and write() blocks for good. + */ - if ((pending & FDCAN_INT_TC) != 0) + if ((pending & (FDCAN_INT_TC | FDCAN_INT_TFE)) != 0) { /* Check if we have disabled the ACKE in the error-handling above * (see FDCAN_TXERR_INTS) to prevent Interrupt-Flooding and * re-enable the error interrupt here again. */ - if ((ie & (FDCAN_INT_PEA | FDCAN_INT_PED)) == 0) + if ((pending & FDCAN_INT_TC) != 0 && + (ie & (FDCAN_INT_PEA | FDCAN_INT_PED)) == 0) { ie |= (FDCAN_INT_PEA | FDCAN_INT_PED); fdcan_putreg(priv, STM32_FDCAN_IE_OFFSET, ie); @@ -2943,18 +3045,9 @@ static int fdcan_interrupt(int irq, void *context, void *arg) fdcan_putreg(priv, STM32_FDCAN_IR_OFFSET, priv->txints); - /* Check all TX buffers */ + /* Tell the upper half about every finished buffer */ - regval = fdcan_getreg(priv, STM32_FDCAN_TXBTO_OFFSET); - for (ndx = 0; ndx < config->ntxfifoq; ndx++) - { - if ((regval & (1 << ndx)) != 0) - { - /* Tell the upper half that the transfer is finished. */ - - can_txdone(dev); - } - } + fdcan_reaptx(dev); } else if ((pending & priv->txints) != 0) { @@ -3169,6 +3262,8 @@ static int fdcan_hw_initialize(struct stm32_fdcan_s *priv) fdcan_putreg(priv, STM32_FDCAN_IR_OFFSET, FDCAN_INT_ALL); + priv->txbusy = 0; + /* Configure FDCAN bit timing */ fdcan_putreg(priv, STM32_FDCAN_NBTP_OFFSET, priv->nbtp); @@ -3215,7 +3310,13 @@ static int fdcan_hw_initialize(struct stm32_fdcan_s *priv) /* CC control register */ regval = fdcan_getreg(priv, STM32_FDCAN_CCCR_OFFSET); - regval &= ~(FDCAN_CCCR_NISO | FDCAN_CCCR_FDOE | FDCAN_CCCR_BRSE); + regval &= ~(FDCAN_CCCR_NISO | FDCAN_CCCR_FDOE | FDCAN_CCCR_BRSE | + FDCAN_CCCR_DAR); + + if (priv->dar) + { + regval |= FDCAN_CCCR_DAR; + } /* Select ISO11898-1 or Non ISO Bosch CAN FD Specification V1.0 */
