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); + } } } }
