Linux CAN drivers development
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Binbin Zhou" <zhoubinbin@loongson.cn>
Cc: "Oleksij Rempel" <o.rempel@pengutronix.de>,
	"Marc Kleine-Budde" <mkl@pengutronix.de>,
	"Oliver Hartkopp" <socketcan@hartkopp.net>,
	"Vincent Mailhol" <mailhol@kernel.org>,
	linux-can@vger.kernel.org
Subject: Re: [PATCH v5 1/2] can: Add Loongson CAN-FD controller driver
Date: Fri, 10 Jul 2026 08:36:09 +0000	[thread overview]
Message-ID: <20260710083609.E0B231F000E9@smtp.kernel.org> (raw)
In-Reply-To: <20507209923b81f7902b3315d1b5837e36b31a67.1783670733.git.zhoubinbin@loongson.cn>

Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
- [High] Missing bounds check on the hardware-provided frame length (`rwcnt`) leads to a kernel heap buffer overflow when reading RX payloads.
- [High] NAPI polling is permanently stalled if SKB allocation fails in the RX path due to skipping `napi_complete_done()`.
- [High] Spurious double processing of TX completions and infinite loop potential due to polling MMIO status immediately after issuing a clear command.
- [Medium] The driver advertises support for `CAN_CTRLMODE_3_SAMPLES` but fails to implement it, leading to silent failures to honor user configuration.
- [Medium] The driver incorrectly resets hardware error counters immediately upon entering bus-off state, bypassing the CAN framework's restart delay mechanism.
- [Medium] Calling `kfree_skb()` with local interrupts disabled.
- [Low] A local variable declaration immediately following a switch case label violates C syntax standards and causes a build failure.
--

--- Patch [1]: [PATCH v5 1/2] can: Add Loongson CAN-FD controller driver ---
commit e34dfafbc093b1981c79c97332825864c52e75bd
Author: Binbin Zhou <zhoubinbin@loongson.cn>
Subject: can: Add Loongson CAN-FD controller driver

Add a new driver for the Loongson CAN-FD controller, which is integrated
into Loongson-2K series SoCs. The controller supports both CAN 2.0 and
CAN-FD protocols, with up to 8 TX buffers and a flexible RX buffer.

> diff --git a/drivers/net/can/loongson_canfd.c b/drivers/net/can/loongson_canfd.c
> new file mode 100644
> index 0000000000000..0e85005a9bb12
> --- /dev/null
> +++ b/drivers/net/can/loongson_canfd.c

[ ... ]

> +static netdev_tx_t loongson_canfd_start_xmit(struct sk_buff *skb, struct net_device *ndev)
> +{
> +	struct canfd_frame *cf = (struct canfd_frame *)skb->data;
> +	struct loongson_canfd_priv *priv = netdev_priv(ndev);
> +	u32 buf_id = 0, tx_stat, i = 0;
> +	unsigned long tx_brp;
> +	u16 tx_bs;
> +
> +	if (can_dropped_invalid_skb(ndev, skb))
> +		return NETDEV_TX_OK;
> +
> +	guard(spinlock_irqsave)(&priv->tx_lock);
> +
> +	if (unlikely(!loongson_canfd_txbuf_freed(priv))) {
> +		netif_stop_queue(ndev);
> +		netdev_err(ndev, "BUG!, no TXB free when queue awake!\n");
> +		return NETDEV_TX_BUSY;
> +	}
> +
> +	regmap_read(priv->regmap, LOONGSON_CANFD_TX_STAT, &tx_stat);
> +	tx_brp = FIELD_GET(REG_TX_STAT_BRP, tx_stat);
> +	tx_bs = FIELD_GET(REG_TX_STAT_BS, tx_stat);
> +
> +	for_each_clear_bit(i, &tx_brp, LOONGSON_CANFD_TXBUF_NUM) {
> +		u32 transmission_complete_mask = 0x3 << (i * 2);
> +
> +		if (!(tx_bs & transmission_complete_mask)) {
> +			buf_id = i;
> +			break;
> +		}
> +	}
> +
> +	if (!loongson_canfd_insert_frame(priv, cf, buf_id, can_is_canfd_skb(skb))) {
> +		netdev_err(ndev, "TXNF set but cannot insert frame into TXTB! HW Bug?");
> +		kfree_skb(skb);
[Severity: Medium]
Is it safe to call kfree_skb() here with local hardware interrupts disabled?

Since this block of code runs with priv->tx_lock held via 
guard(spinlock_irqsave), could we use dev_kfree_skb_any() instead to avoid 
potentially sleeping or violating atomic context APIs?

> +		ndev->stats.tx_dropped++;
> +		return NETDEV_TX_OK;
> +	}

[ ... ]

> +static int loongson_canfd_rx(struct net_device *ndev)
> +{
> +	struct loongson_canfd_priv *priv = netdev_priv(ndev);
> +	struct net_device_stats *stats = &ndev->stats;
> +	u32 meta0, meta1, dlc, rwcnt, dbcnt, i, data;
> +	struct canfd_frame *cfd;
> +	struct can_frame *ccf;
> +	struct sk_buff *skb;
> +	bool is_can_fd;
> +
> +	regmap_read(priv->regmap, LOONGSON_CANFD_RX_DATA, &meta0);
> +	regmap_read(priv->regmap, LOONGSON_CANFD_RX_DATA, &meta1);
> +
> +	/* Number of characters received */
> +	rwcnt = FIELD_GET(REG_FRAME_META1_RWCNT, meta1);
> +	if (!rwcnt)
> +		return -EAGAIN;
> +	dbcnt = (rwcnt - 2) * LOONGSON_CANFD_DW_BYTE;
[Severity: High]
Can dbcnt underflow if the hardware provides a value of 1 for rwcnt?

If a malfunctioning or malicious CAN controller provides an rwcnt of 1, 
the subtraction will underflow, causing dbcnt to become a very large number. 
This would lead to a kernel heap buffer overflow when the subsequent code 
reads payload data into cfd->data.

Could we add a bounds check to ensure rwcnt >= 2 and dbcnt <= CANFD_MAX_DLEN?

> +
> +	/* Flexible Data-rate Format */

[ ... ]

> +static void loongson_canfd_err_interrupt(struct net_device *ndev, u32 isr)
> +{
> +	struct loongson_canfd_priv *priv = netdev_priv(ndev);
> +	struct net_device_stats *stats = &ndev->stats;
> +	struct can_berr_counter bec;
> +	u32 capt, alc, mask = 0;
> +	enum can_state state;
> +	struct can_frame *cf;
> +	struct sk_buff *skb;
> +
> +	loongson_canfd_get_bec(priv, &bec);
> +
> +	state = loongson_canfd_read_fault_state(ndev);

[ ... ]

> +	if ((isr & REG_INT_STAT_FCSI) || (isr & REG_INT_STAT_EWLI)) {
> +		netdev_info(ndev, "state changes from %s to %s\n",
> +			    can_get_state_str(priv->can.state), can_get_state_str(state));
> +
> +		if (priv->can.state == state)
> +			netdev_warn(ndev, "cur and pre state is the same!(miss intr?)\n");
> +
> +		mask |= REG_INT_STAT_FCSI | REG_INT_STAT_EWLI;
> +		priv->can.state = state;
> +		switch (state) {
> +		case CAN_STATE_BUS_OFF:
> +			priv->can.can_stats.bus_off++;
> +			if (priv->can.restart_ms)
> +				regmap_write(priv->regmap, LOONGSON_CANFD_CMD,
> +					     REG_CMD_ERCRST);
[Severity: Medium]
Does resetting the hardware error counters immediately upon entering bus-off
state bypass the CAN framework's restart delay mechanism?

Clearing the hardware error counters here forces the controller to attempt
recovery instantly, which circumvents the software-defined isolation delay
(restart_ms) that is supposed to keep the device off the bus before
can_restart() eventually re-enables it.

> +
> +			can_bus_off(ndev);
> +			if (skb)
> +				cf->can_id |= CAN_ERR_BUSOFF;
> +			break;

[ ... ]

> +static int loongson_canfd_rx_napi(struct napi_struct *napi, int quota)
> +{
> +	struct net_device *ndev = napi->dev;
> +	struct loongson_canfd_priv *priv = netdev_priv(ndev);
> +	int work_done = 0, ret = 1;
> +	bool rxbuf_is_empty;
> +	u32 sts;
> +
> +	rxbuf_is_empty = loongson_canfd_rxbuf_empty(priv);
> +
> +	while (!rxbuf_is_empty && work_done < quota && ret > 0) {
> +		ret = loongson_canfd_rx(ndev);
> +		work_done++;
> +		rxbuf_is_empty = loongson_canfd_rxbuf_empty(priv);
> +	}
> +
> +	/* Check for RX FIFO Overflow */

[ ... ]

> +	if (rxbuf_is_empty && ret != 0) {
> +		if (napi_complete_done(napi, work_done)) {
[Severity: High]
Can NAPI polling stall permanently if SKB allocation fails in the RX path?

If alloc_can_skb() fails due to memory pressure, loongson_canfd_rx() returns
0. The NAPI while loop breaks early because ret > 0 evaluates to false. 

Because of the condition above (ret != 0), napi_complete_done() is then 
skipped. This leaves the NAPI instance stuck in the NAPI_STATE_SCHED state 
without unmasking the RX interrupt, permanently deadlocking the receive path 
until the interface is brought down and back up.

> +			/*
> +			 * Clear and enable RBNEI. It is level-triggered,
> +			 * so there is no race condition.
> +			 */
> +			regmap_write(priv->regmap, LOONGSON_CANFD_INT_STAT, REG_INT_STAT_RBNEI);

[ ... ]

> +static void loongson_canfd_tx_interrupt(struct net_device *ndev)
> +{
> +	struct loongson_canfd_priv *priv = netdev_priv(ndev);
> +	struct net_device_stats *stats = &ndev->stats;
> +	enum loongson_canfd_txbuf_sts sts;
> +	bool some_buffers_processed;
> +	u32 buf_id;
> +
> +	do {
> +		some_buffers_processed = false;
> +
> +		guard(spinlock_irqsave)(&priv->tx_lock);
> +
> +		while ((buf_id = loongson_canfd_get_txbuf_id(priv, &sts)) <
> +			LOONGSON_CANFD_TXBUF_NUM) {
> +			switch (sts) {
> +			case TX_BS_VALID:
> +				stats->tx_bytes += can_get_echo_skb(ndev, buf_id, NULL);
> +				stats->tx_packets++;
> +				break;
> +			case TX_BS_FAIL:
> +				u32 cnt;
[Severity: Low]
Does this local variable declaration directly following a label violate C
syntax rules?

Under standard C11 rules (gnu11), a label must be attached to a statement, 
and a declaration is not considered a statement. This can cause compilation 
to fail with modern GCC versions. 

Could we move this declaration outside the switch block, or wrap the logic 
for this case inside braces?

> +				/*
> +				 * This indicated that retransmit limit has been reached.
> +				 * Obviously we should not echo the frame, but also not indicate any
> +				 * kind of error. If desired, it was already reported (possible
> +				 * multiple times) on each arbitration lost.
> +				 */

[ ... ]

> +			loongson_canfd_set_txbuf_cmd(ndev, TXT_CMD_SR_CLEAN, buf_id);
> +			some_buffers_processed = true;
> +		}
> +
> +		/*
> +		 * If no buffers were processed this time, we cannot clear - that would introduce
> +		 * a race condition.
> +		 */
> +		if (some_buffers_processed) {
> +			/*
> +			 * Clear the interrupt again. We do not want to receive again interrupt
> +			 * for the buffer already handled. If it is the last finished one then
> +			 * it would cause log of spurious interrupt.
> +			 */
> +			regmap_write(priv->regmap, LOONGSON_CANFD_INT_STAT, REG_INT_STAT_TXBHCI);
> +		}
> +	} while (some_buffers_processed);
[Severity: High]
Could this loop result in spurious double-processing of TX completions?

If the MMIO write to clear the status bit (TXT_CMD_SR_CLEAN) takes time to
propagate through the hardware, the next immediate iteration of the loop 
could read the old status from LOONGSON_CANFD_TX_STAT.

This would cause the driver to process the exact same TX buffer multiple 
times, inflating tx_packets or tx_dropped and spamming the logs with warnings 
until the hardware finally updates the register.

> +
> +	guard(spinlock_irqsave)(&priv->tx_lock);

[ ... ]

> +static int loongson_canfd_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct loongson_canfd_priv *priv;
> +	struct net_device *ndev;
> +	struct regmap *regmap;
> +	struct resource *res;
> +	void __iomem *base;
> +	int ret = 0, irq;
> +	u32 clk_rate;

[ ... ]

> +	priv->can.ctrlmode_supported = CAN_CTRLMODE_LOOPBACK | CAN_CTRLMODE_LISTENONLY |
> +				       CAN_CTRLMODE_3_SAMPLES | CAN_CTRLMODE_ONE_SHOT |
[Severity: Medium]
Does the driver actually support CAN_CTRLMODE_3_SAMPLES in hardware?

The driver advertises 3-samples mode here, allowing userspace to request it, 
but functions like loongson_canfd_set_bittiming() and loongson_canfd_set_btr() 
do not appear to check for this flag or program the hardware appropriately 
to use multiple samples. 

Could this lead to silent failures to honor the user's requested configuration?

> +				       CAN_CTRLMODE_BERR_REPORTING | CAN_CTRLMODE_FD |
> +				       CAN_CTRLMODE_PRESUME_ACK | CAN_CTRLMODE_FD_NON_ISO |
> +				       CAN_CTRLMODE_TDC_AUTO | CAN_CTRLMODE_CC_LEN8_DLC;

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1783670733.git.zhoubinbin@loongson.cn?part=1

  reply	other threads:[~2026-07-10  8:36 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-10  8:22 [PATCH v5 0/2] Add Loongson CAN-FD controller driver Binbin Zhou
2026-07-10  8:22 ` [PATCH v5 1/2] can: " Binbin Zhou
2026-07-10  8:36   ` sashiko-bot [this message]
2026-07-11  3:05   ` Huacai Chen
2026-07-10  8:22 ` [PATCH v5 2/2] can: loongson_canfd: Add RXDMA support Binbin Zhou
2026-07-10  8:32   ` sashiko-bot

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260710083609.E0B231F000E9@smtp.kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=linux-can@vger.kernel.org \
    --cc=mailhol@kernel.org \
    --cc=mkl@pengutronix.de \
    --cc=o.rempel@pengutronix.de \
    --cc=sashiko-reviews@lists.linux.dev \
    --cc=socketcan@hartkopp.net \
    --cc=zhoubinbin@loongson.cn \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox