Re: C_CAN/D_CAN bug and fix

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hey all,

I ran into this same problem, and picked up the patches.

On ma, 23 jul 2018 13:36:14 +0200, Marc Kleine-Budde wrote:
> On 06/21/2018 03:20 PM, Wolfgang Grandegger wrote:
...
> >>> Looking to the code, I see a problem with reading the status register
> >>> (C_CAN_STS_REG). It should *only* be read if bit 15 of the interrupt
> >>> status register (C_CAN_INT_REG) is set/pending. According to the manual,
> >>> reading the status register will also clear the bit in C_CAN_INT_REG.
> >>> This could explain lost (bus-off) status interrupts and also the strange
> >>> messages I mentioned above.
> >>>
> >>> Wolfgang.
> >>
> >> It sounds plausible. I can try it. Have you got a good datasheet for D_Can ?
> > 
> > I have attached my (untested) patch. TXOK and RXOK is still handled
> > unconditionally, but that's maybe less critical.
> 
> Has someone tested this patch?

I believe Wolfgang's analysis is right, so I tested the patch and it
doesn't work, because C_CAN_INT_REG is already read in c_can_isr.
On my BeagleBone-like board, the INT_STS_PENDING bit is gone by the
second read.

I modified the patch with an atomic_t in priv, which holds the relevant
bit.

commit 388d1d0d53fb1c000e43e1d58b91e7782d60fcfe
Author: Kurt Van Dijck <dev.kurt@xxxxxxxxxxxxxxxxxxxxxx>
Date:   Thu Jul 4 22:37:11 2019

    net can c_can: only read status register after status irq

diff --git a/drivers/net/can/c_can/c_can.h b/drivers/net/can/c_can/c_can.h
index 8acdc7f..d5567a7 100644
--- a/drivers/net/can/c_can/c_can.h
+++ b/drivers/net/can/c_can/c_can.h
@@ -198,6 +198,7 @@ struct c_can_priv {
 	struct net_device *dev;
 	struct device *device;
 	atomic_t tx_active;
+	atomic_t sie_pending;
 	unsigned long tx_dir;
 	int last_status;
 	u16 (*read_reg) (const struct c_can_priv *priv, enum reg index);
diff --git a/drivers/net/can/c_can/c_can.c b/drivers/net/can/c_can/c_can.c
index 606b7d8..61c7331 100644
--- a/drivers/net/can/c_can/c_can.c
+++ b/drivers/net/can/c_can/c_can.c
@@ -97,6 +97,9 @@
 #define BTR_TSEG2_SHIFT		12
 #define BTR_TSEG2_MASK		(0x7 << BTR_TSEG2_SHIFT)
 
+/* interrupt register */
+#define INT_STS_PENDING		0x8000
+
 /* brp extension register */
 #define BRP_EXT_BRPE_MASK	0x0f
 #define BRP_EXT_BRPE_SHIFT	0
@@ -1029,40 +1032,48 @@ static int c_can_poll(struct napi_struct *napi, int quota)
 	u16 curr, last = priv->last_status;
 	int work_done = 0;
 
-	priv->last_status = curr = priv->read_reg(priv, C_CAN_STS_REG);
-	/* Ack status on C_CAN. D_CAN is self clearing */
-	if (priv->type != BOSCH_D_CAN)
-		priv->write_reg(priv, C_CAN_STS_REG, LEC_UNUSED);
+	/* Only read the status register if a status interrupt was pending */
+	if (atomic_xchg(&priv->sie_pending, 0)) {
+		priv->last_status = curr = priv->read_reg(priv, C_CAN_STS_REG);
+		/* Ack status on C_CAN. D_CAN is self clearing */
+		if (priv->type != BOSCH_D_CAN)
+			priv->write_reg(priv, C_CAN_STS_REG, LEC_UNUSED);
+
+		/* handle state changes */
+		if ((curr & STATUS_EWARN) && (!(last & STATUS_EWARN))) {
+			netdev_dbg(dev, "entered error warning state\n");
+			work_done +=
+				c_can_handle_state_change(dev,
+							  C_CAN_ERROR_WARNING);
+		}
 
-	/* handle state changes */
-	if ((curr & STATUS_EWARN) && (!(last & STATUS_EWARN))) {
-		netdev_dbg(dev, "entered error warning state\n");
-		work_done += c_can_handle_state_change(dev, C_CAN_ERROR_WARNING);
-	}
+		if ((curr & STATUS_EPASS) && (!(last & STATUS_EPASS))) {
+			netdev_dbg(dev, "entered error passive state\n");
+			work_done +=
+				c_can_handle_state_change(dev,
+							  C_CAN_ERROR_PASSIVE);
+		}
 
-	if ((curr & STATUS_EPASS) && (!(last & STATUS_EPASS))) {
-		netdev_dbg(dev, "entered error passive state\n");
-		work_done += c_can_handle_state_change(dev, C_CAN_ERROR_PASSIVE);
-	}
+		if ((curr & STATUS_BOFF) && (!(last & STATUS_BOFF))) {
+			netdev_dbg(dev, "entered bus off state\n");
+			work_done +=
+				c_can_handle_state_change(dev, C_CAN_BUS_OFF);
+			goto end;
+		}
 
-	if ((curr & STATUS_BOFF) && (!(last & STATUS_BOFF))) {
-		netdev_dbg(dev, "entered bus off state\n");
-		work_done += c_can_handle_state_change(dev, C_CAN_BUS_OFF);
-		goto end;
-	}
+		/* handle bus recovery events */
+		if ((!(curr & STATUS_BOFF)) && (last & STATUS_BOFF)) {
+			netdev_dbg(dev, "left bus off state\n");
+			priv->can.state = CAN_STATE_ERROR_ACTIVE;
+		}
+		if ((!(curr & STATUS_EPASS)) && (last & STATUS_EPASS)) {
+			netdev_dbg(dev, "left error passive state\n");
+			priv->can.state = CAN_STATE_ERROR_ACTIVE;
+		}
 
-	/* handle bus recovery events */
-	if ((!(curr & STATUS_BOFF)) && (last & STATUS_BOFF)) {
-		netdev_dbg(dev, "left bus off state\n");
-		priv->can.state = CAN_STATE_ERROR_ACTIVE;
+		/* handle lec errors on the bus */
+		work_done += c_can_handle_bus_err(dev, curr & LEC_MASK);
 	}
-	if ((!(curr & STATUS_EPASS)) && (last & STATUS_EPASS)) {
-		netdev_dbg(dev, "left error passive state\n");
-		priv->can.state = CAN_STATE_ERROR_ACTIVE;
-	}
-
-	/* handle lec errors on the bus */
-	work_done += c_can_handle_bus_err(dev, curr & LEC_MASK);
 
 	/* Handle Tx/Rx events. We do this unconditionally */
 	work_done += c_can_do_rx_poll(dev, (quota - work_done));
@@ -1083,10 +1094,16 @@ static irqreturn_t c_can_isr(int irq, void *dev_id)
 {
 	struct net_device *dev = (struct net_device *)dev_id;
 	struct c_can_priv *priv = netdev_priv(dev);
+	int intreg;
 
-	if (!priv->read_reg(priv, C_CAN_INT_REG))
+	intreg = priv->read_reg(priv, C_CAN_INT_REG);
+	if (!intreg)
 		return IRQ_NONE;
 
+	/* save for later use */
+	if (intreg & INT_STS_PENDING)
+		atomic_set(&priv->sie_pending, 1);
+
 	/* disable all interrupts and schedule the NAPI */
 	c_can_irq_control(priv, false);
 	napi_schedule(&priv->napi);



[Index of Archives]     [Automotive Discussions]     [Linux ARM Kernel]     [Linux ARM]     [Linux Omap]     [Fedora ARM]     [IETF Annouce]     [Security]     [Bugtraq]     [Linux]     [Linux OMAP]     [Linux MIPS]     [eCos]     [Asterisk Internet PBX]     [Linux API]     [CAN Bus]

  Powered by Linux