can: slcan: extend the protocol with error info
authorDario Binacchi <dario.binacchi@amarulasolutions.com>
Tue, 28 Jun 2022 16:31:35 +0000 (18:31 +0200)
committerMarc Kleine-Budde <mkl@pengutronix.de>
Sun, 3 Jul 2022 09:34:44 +0000 (11:34 +0200)
It extends the protocol to receive the adapter CAN communication errors
and forward them to the netdev upper levels.

Link: https://lore.kernel.org/all/20220628163137.413025-12-dario.binacchi@amarulasolutions.com
Signed-off-by: Dario Binacchi <dario.binacchi@amarulasolutions.com>
Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
drivers/net/can/slcan/slcan-core.c

index c1fd1e934d93b64c078d7ece27909aef417658eb..4269b2267be2229aa4e36f4d3f8f5c2ddc7e3ba0 100644 (file)
@@ -175,7 +175,7 @@ int slcan_enable_err_rst_on_open(struct net_device *ndev, bool on)
   ************************************************************************/
 
 /* Send one completely decapsulated can_frame to the network layer */
-static void slc_bump(struct slcan *sl)
+static void slc_bump_frame(struct slcan *sl)
 {
        struct sk_buff *skb;
        struct can_frame *cf;
@@ -254,6 +254,144 @@ decode_failed:
        dev_kfree_skb(skb);
 }
 
+/* An error frame can contain more than one type of error.
+ *
+ * Examples:
+ *
+ * e1a : len 1, errors: ACK error
+ * e3bcO: len 3, errors: Bit0 error, CRC error, Tx overrun error
+ */
+static void slc_bump_err(struct slcan *sl)
+{
+       struct net_device *dev = sl->dev;
+       struct sk_buff *skb;
+       struct can_frame *cf;
+       char *cmd = sl->rbuff;
+       bool rx_errors = false, tx_errors = false, rx_over_errors = false;
+       int i, len;
+
+       /* get len from sanitized ASCII value */
+       len = cmd[1];
+       if (len >= '0' && len < '9')
+               len -= '0';
+       else
+               return;
+
+       if ((len + SLC_CMD_LEN + 1) > sl->rcount)
+               return;
+
+       skb = alloc_can_err_skb(dev, &cf);
+
+       if (skb)
+               cf->can_id |= CAN_ERR_PROT | CAN_ERR_BUSERROR;
+
+       cmd += SLC_CMD_LEN + 1;
+       for (i = 0; i < len; i++, cmd++) {
+               switch (*cmd) {
+               case 'a':
+                       netdev_dbg(dev, "ACK error\n");
+                       tx_errors = true;
+                       if (skb) {
+                               cf->can_id |= CAN_ERR_ACK;
+                               cf->data[3] = CAN_ERR_PROT_LOC_ACK;
+                       }
+
+                       break;
+               case 'b':
+                       netdev_dbg(dev, "Bit0 error\n");
+                       tx_errors = true;
+                       if (skb)
+                               cf->data[2] |= CAN_ERR_PROT_BIT0;
+
+                       break;
+               case 'B':
+                       netdev_dbg(dev, "Bit1 error\n");
+                       tx_errors = true;
+                       if (skb)
+                               cf->data[2] |= CAN_ERR_PROT_BIT1;
+
+                       break;
+               case 'c':
+                       netdev_dbg(dev, "CRC error\n");
+                       rx_errors = true;
+                       if (skb) {
+                               cf->data[2] |= CAN_ERR_PROT_BIT;
+                               cf->data[3] = CAN_ERR_PROT_LOC_CRC_SEQ;
+                       }
+
+                       break;
+               case 'f':
+                       netdev_dbg(dev, "Form Error\n");
+                       rx_errors = true;
+                       if (skb)
+                               cf->data[2] |= CAN_ERR_PROT_FORM;
+
+                       break;
+               case 'o':
+                       netdev_dbg(dev, "Rx overrun error\n");
+                       rx_over_errors = true;
+                       rx_errors = true;
+                       if (skb) {
+                               cf->can_id |= CAN_ERR_CRTL;
+                               cf->data[1] = CAN_ERR_CRTL_RX_OVERFLOW;
+                       }
+
+                       break;
+               case 'O':
+                       netdev_dbg(dev, "Tx overrun error\n");
+                       tx_errors = true;
+                       if (skb) {
+                               cf->can_id |= CAN_ERR_CRTL;
+                               cf->data[1] = CAN_ERR_CRTL_TX_OVERFLOW;
+                       }
+
+                       break;
+               case 's':
+                       netdev_dbg(dev, "Stuff error\n");
+                       rx_errors = true;
+                       if (skb)
+                               cf->data[2] |= CAN_ERR_PROT_STUFF;
+
+                       break;
+               default:
+                       if (skb)
+                               dev_kfree_skb(skb);
+
+                       return;
+               }
+       }
+
+       if (rx_errors)
+               dev->stats.rx_errors++;
+
+       if (rx_over_errors)
+               dev->stats.rx_over_errors++;
+
+       if (tx_errors)
+               dev->stats.tx_errors++;
+
+       if (skb)
+               netif_rx(skb);
+}
+
+static void slc_bump(struct slcan *sl)
+{
+       switch (sl->rbuff[0]) {
+       case 'r':
+               fallthrough;
+       case 't':
+               fallthrough;
+       case 'R':
+               fallthrough;
+       case 'T':
+               return slc_bump_frame(sl);
+       case 'e':
+               return slc_bump_err(sl);
+       default:
+               return;
+       }
+}
+
 /* parse tty input stream */
 static void slcan_unesc(struct slcan *sl, unsigned char s)
 {