This is an automated email from the ASF dual-hosted git repository.

casaroli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git

commit f58c7980b2eda7f4687dbaf498dd2cbce79b5ff8
Author: wangjinjing1 <[email protected]>
AuthorDate: Mon Dec 29 22:19:17 2025 +0800

    net/can: Add CAN_RAW_LOOPBACK and CAN_RAW_RECV_OWN_MSGS support.
    
    Implement TX confirmation as in Linux SocketCAN:
    
    1. sendmsg always sets iob->io_conn; a socket receives its own frames
       only with CAN_RAW_RECV_OWN_MSGS enabled.
    2. CAN_RAW_LOOPBACK is on by default.  A sent frame is looped back to
       the local sockets either by the device driver (loopback devices) or
       by the CAN stack itself.
    3. Frames received by the socket that sent them are flagged with
       MSG_CONFIRM.
    
    Ported to upstream: io_conn is already part of struct iob_s, so only
    its reset and clone are changed; devif_poll.c includes <nuttx/net/can.h>
    for can_iob_clone() and can_input().  The copy is delivered by
    can_loopback(), which points the device at the copy, runs can_input()
    and then restores d_iob, d_buf, d_len and d_sndlen exactly as the
    driver left them.  Upstream drivers either send the frame in the poll
    callback and clear the buffer, or, with a flat buffer, leave the frame
    in d_iob for devif_poll() to copy and send; in both cases the frame is
    sent, and the callback's return value only tells whether to stop the
    poll, so the copy is looped back after every callback.  The behavior is
    documented in socketcan.rst.
    
    Assisted-by: Claude:claude-opus-5-5
    Signed-off-by: wangjinjing1 <[email protected]>
    Signed-off-by: p-szafonimateusz <[email protected]>
---
 Documentation/os/networking/socketcan.rst | 19 +++++++++
 mm/iob/iob_alloc.c                        |  5 +--
 mm/iob/iob_clone.c                        |  2 +
 net/can/can_conn.c                        |  9 +++++
 net/can/can_recvmsg.c                     | 10 +++++
 net/can/can_sendmsg.c                     |  4 ++
 net/can/can_sendmsg_buffered.c            |  2 +
 net/can/can_sockif.c                      |  7 ++++
 net/devif/devif_poll.c                    | 67 ++++++++++++++++++++++++++++++-
 9 files changed, 120 insertions(+), 5 deletions(-)

diff --git a/Documentation/os/networking/socketcan.rst 
b/Documentation/os/networking/socketcan.rst
index 4b2ec8521af..8db7955979a 100644
--- a/Documentation/os/networking/socketcan.rst
+++ b/Documentation/os/networking/socketcan.rst
@@ -136,6 +136,25 @@ SocketCAN uses the standard socket programming model:
    /* Receive CAN frame */
    recv(sock, &frame, sizeof(frame), 0);
 
+Local Loopback
+--------------
+
+As in Linux, ``CAN_RAW_LOOPBACK`` is enabled by default: a frame sent on a
+CAN_RAW socket is also delivered to the other CAN_RAW sockets bound to the
+same interface on the same node (subject to their filters).  The sending
+socket receives its own frames only with ``CAN_RAW_RECV_OWN_MSGS``; those
+copies are flagged with ``MSG_CONFIRM`` in ``msg_flags`` and can be used as a
+transmit confirmation.  Disable the loopback with:
+
+.. code-block:: c
+
+   int off = 0;
+   setsockopt(sock, SOL_CAN_RAW, CAN_RAW_LOOPBACK, &off, sizeof(off));
+
+If the network device is not a loopback device, the stack loops the frame
+back itself after the driver accepted it.  Looped back frames are counted in
+the received CAN statistics.
+
 Features
 --------
 
diff --git a/mm/iob/iob_alloc.c b/mm/iob/iob_alloc.c
index 5674948e2c9..e06f5518805 100644
--- a/mm/iob/iob_alloc.c
+++ b/mm/iob/iob_alloc.c
@@ -96,9 +96,7 @@ static FAR struct iob_s *iob_alloc_committed(void)
       iob->io_len    = 0;    /* Length of the data in the entry */
       iob->io_offset = 0;    /* Offset to the beginning of data */
       iob->io_pktlen = 0;    /* Total length of the packet */
-#ifdef CONFIG_NET_TIMESTAMP
       iob->io_conn   = NULL;
-#endif
     }
 
   spin_unlock_irqrestore(&g_iob_lock, flags);
@@ -138,9 +136,8 @@ static FAR struct iob_s *iob_tryalloc_internal(bool 
throttled)
           iob->io_len    = 0;    /* Length of the data in the entry */
           iob->io_offset = 0;    /* Offset to the beginning of data */
           iob->io_pktlen = 0;    /* Total length of the packet */
-#ifdef CONFIG_NET_TIMESTAMP
           iob->io_conn   = NULL;
-#endif
+
           return iob;
         }
     }
diff --git a/mm/iob/iob_clone.c b/mm/iob/iob_clone.c
index 86316bc4519..fadb56603a8 100644
--- a/mm/iob/iob_clone.c
+++ b/mm/iob/iob_clone.c
@@ -121,6 +121,8 @@ int iob_clone_partial(FAR struct iob_s *iob1, unsigned int 
len,
   unsigned int avail2;
   int ret;
 
+  iob2->io_conn = iob1->io_conn;
+
   /* Copy the total packet size from the I/O buffer at the head of the
    * chain.
    */
diff --git a/net/can/can_conn.c b/net/can/can_conn.c
index e14f256b8eb..7547aaf333b 100644
--- a/net/can/can_conn.c
+++ b/net/can/can_conn.c
@@ -298,6 +298,15 @@ FAR struct can_conn_s *can_active(FAR struct net_driver_s 
*dev,
       if ((conn->dev == NULL && _SS_ISBOUND(conn->sconn.s_flags)) ||
           conn->dev == dev)
         {
+          if (dev->d_iob->io_conn == &conn->sconn
+#ifdef CONFIG_NET_CANPROTO_OPTIONS
+              && !_SO_GETOPT(conn->sconn.s_options, CAN_RAW_RECV_OWN_MSGS)
+#endif
+             )
+            {
+              continue; /* Skip own messages */
+            }
+
 #ifdef CONFIG_NET_CANPROTO_OPTIONS
           if (can_recv_filter(conn, can_id) == 0)
             {
diff --git a/net/can/can_recvmsg.c b/net/can/can_recvmsg.c
index f8bf9574f26..215660b2993 100644
--- a/net/can/can_recvmsg.c
+++ b/net/can/can_recvmsg.c
@@ -161,6 +161,11 @@ static size_t can_recvfrom_newdata(FAR struct net_driver_s 
*dev,
                        pstate->pr_conn->sconn.s_options);
 #endif
 
+  if (dev->d_iob->io_conn == &pstate->pr_conn->sconn)
+    {
+      pstate->pr_msg->msg_flags |= MSG_CONFIRM;
+    }
+
   recvlen = MIN(pstate->pr_msg->msg_iov->iov_len, dev->d_len);
 
   /* Copy the new packet data into the user buffer */
@@ -265,6 +270,11 @@ static inline int can_readahead(struct can_recvfrom_s 
*pstate)
                            conn->sconn.s_options);
 #endif
 
+      if (iob->io_conn == &conn->sconn)
+        {
+          pstate->pr_msg->msg_flags |= MSG_CONFIRM;
+        }
+
       /* Transfer that buffered data from the I/O buffer chain into
        * the user buffer.
        */
diff --git a/net/can/can_sendmsg.c b/net/can/can_sendmsg.c
index 5959ba3ad62..c40b8eaebe7 100644
--- a/net/can/can_sendmsg.c
+++ b/net/can/can_sendmsg.c
@@ -63,6 +63,7 @@
 struct send_s
 {
   FAR struct devif_callback_s *snd_cb; /* Reference to callback instance */
+  FAR struct can_conn_s  *snd_conn;    /* Reference to the CAN connection */
   sem_t                   snd_sem;     /* Used to wake up the waiting thread */
   FAR const uint8_t      *snd_buffer;  /* Points to the buffer of data to send 
*/
   size_t                  snd_buflen;  /* Number of bytes in the buffer to 
send */
@@ -120,6 +121,8 @@ static uint32_t psock_send_eventhandler(FAR struct 
net_driver_s *dev,
               goto end_wait;
             }
 
+          dev->d_iob->io_conn = &pstate->snd_conn->sconn;
+
           pstate->snd_sent = pstate->snd_buflen;
           if (pstate->pr_msglen > 0) /* concat cmsg data after packet */
             {
@@ -233,6 +236,7 @@ ssize_t can_sendmsg(FAR struct socket *psock, FAR const 
struct msghdr *msg,
 
   state.snd_buflen = msg->msg_iov->iov_len;  /* bytes to send */
   state.snd_buffer = msg->msg_iov->iov_base; /* Buffer to send from */
+  state.snd_conn   = conn;
 
 #ifdef CONFIG_NET_CAN_RAW_TX_DEADLINE
   if (msg->msg_controllen > sizeof(struct cmsghdr))
diff --git a/net/can/can_sendmsg_buffered.c b/net/can/can_sendmsg_buffered.c
index 2fca441d212..d2696a75e3f 100644
--- a/net/can/can_sendmsg_buffered.c
+++ b/net/can/can_sendmsg_buffered.c
@@ -303,6 +303,8 @@ ssize_t can_sendmsg(FAR struct socket *psock, FAR const 
struct msghdr *msg,
   iob_reserve(wb_iob, CONFIG_NET_LL_GUARDSIZE);
   iob_update_pktlen(wb_iob, 0, false);
 
+  wb_iob->io_conn = &conn->sconn;
+
   /* Copy the user data into the write buffer.  We cannot wait for
    * buffer space if the socket was opened non-blocking.
    */
diff --git a/net/can/can_sockif.c b/net/can/can_sockif.c
index 67d4aafbbd3..32cffb8f741 100644
--- a/net/can/can_sockif.c
+++ b/net/can/can_sockif.c
@@ -234,6 +234,13 @@ static int can_setup(FAR struct socket *psock)
 #endif
       nxrmutex_init(&conn->sconn.s_lock);
 
+#ifdef CONFIG_NET_CANPROTO_OPTIONS
+
+      /* By default loopback is enabled */
+
+      _SO_SETOPT(conn->sconn.s_options, CAN_RAW_LOOPBACK);
+#endif
+
       /* Attach the connection instance to the socket */
 
       psock->s_conn = conn;
diff --git a/net/devif/devif_poll.c b/net/devif/devif_poll.c
index 059df2b2833..0529c7987d5 100644
--- a/net/devif/devif_poll.c
+++ b/net/devif/devif_poll.c
@@ -30,6 +30,7 @@
 #include <nuttx/debug.h>
 
 #include <nuttx/clock.h>
+#include <nuttx/net/can.h>
 #include <nuttx/net/netconfig.h>
 #include <nuttx/net/netdev.h>
 #include <nuttx/net/net.h>
@@ -277,6 +278,46 @@ devif_poll_pkt_connections(FAR struct net_driver_s *dev,
 }
 #endif /* CONFIG_NET_PKT */
 
+/****************************************************************************
+ * Name: can_loopback
+ *
+ * Description:
+ *   Deliver a copy of a sent CAN frame to the local CAN sockets.  The
+ *   device buffer state is saved and restored, so the driver and
+ *   devif_poll() continue with the frame exactly as if there were no
+ *   loopback.
+ *
+ * Input Parameters:
+ *   dev - The CAN device that sent the frame
+ *   iob - A copy of the sent frame, freed by this function
+ *
+ ****************************************************************************/
+
+#ifdef CONFIG_NET_CAN
+static void can_loopback(FAR struct net_driver_s *dev, FAR struct iob_s *iob)
+{
+  FAR struct iob_s *d_iob = dev->d_iob;
+  FAR uint8_t *d_buf = dev->d_buf;
+  uint16_t d_len = dev->d_len;
+  uint16_t d_sndlen = dev->d_sndlen;
+
+  dev->d_iob = iob;
+  dev->d_buf = NETLLBUF;
+  dev->d_len = iob->io_pktlen;
+
+  can_input(dev);
+
+  /* Free the copy unless a socket kept it */
+
+  netdev_iob_release(dev);
+
+  dev->d_iob    = d_iob;
+  dev->d_buf    = d_buf;
+  dev->d_len    = d_len;
+  dev->d_sndlen = d_sndlen;
+}
+#endif /* CONFIG_NET_CAN */
+
 /****************************************************************************
  * Name: devif_poll_can_connections
  *
@@ -316,7 +357,31 @@ devif_poll_can_connections(FAR struct net_driver_s *dev,
 
           if (dev->d_len > 0)
             {
-              bstop = callback(dev);
+              if (!IFF_IS_LOOPBACK(dev->d_flags)
+#ifdef CONFIG_NET_CANPROTO_OPTIONS
+                  && _SO_GETOPT(can_conn->sconn.s_options, CAN_RAW_LOOPBACK)
+#endif
+                 )
+                {
+                  FAR struct iob_s *iob = can_iob_clone(dev);
+
+                  /* The frame is always sent: the callback either sends it
+                   * or, for drivers with a flat buffer, leaves it in d_iob
+                   * for devif_poll() to send.  Its return value only tells
+                   * whether to stop the poll.
+                   */
+
+                  bstop = callback(dev);
+
+                  if (iob != NULL)
+                    {
+                      can_loopback(dev, iob);
+                    }
+                }
+              else
+                {
+                  bstop = callback(dev);
+                }
             }
         }
     }

Reply via email to