diff --git a/include/nuttx/net/netdev.h b/include/nuttx/net/netdev.h index df9ecefc22..0d35828de3 100644 --- a/include/nuttx/net/netdev.h +++ b/include/nuttx/net/netdev.h @@ -442,6 +442,18 @@ struct net_driver_s struct netdev_statistics_s d_statistics; #endif +#if defined(CONFIG_NET_TIMESTAMP) + /* Reception timestamp of packet being currently processed. + * If CONFIG_ARCH_HAVE_NETDEV_TIMESTAMP is true, the timestamp is provided + * by hardware driver. Otherwise it is filled in by kernel when packet + * enters ipv4_input or ipv6_input. + * + * The timestamp is in CLOCK_REALTIME. + */ + + struct timespec d_rxtime; +#endif + /* Application callbacks: * * Network device event handlers are retained in a 'list' and are called diff --git a/net/Kconfig b/net/Kconfig index f2babaa273..1eca79c99e 100644 --- a/net/Kconfig +++ b/net/Kconfig @@ -23,6 +23,10 @@ config ARCH_HAVE_NETDEV_STATISTICS bool default n +config ARCH_HAVE_NETDEV_TIMESTAMP + bool + default n + config NET_WRITE_BUFFERS bool default n diff --git a/net/devif/ipv4_input.c b/net/devif/ipv4_input.c index 5b8f9fc7fc..4e7f2eb16f 100644 --- a/net/devif/ipv4_input.c +++ b/net/devif/ipv4_input.c @@ -491,6 +491,12 @@ int ipv4_input(FAR struct net_driver_s *dev) FAR uint8_t *buf; int ret; + /* Store reception timestamp if enabled and not provided by hardware. */ + +#if defined(CONFIG_NET_TIMESTAMP) && !defined(CONFIG_ARCH_HAVE_NETDEV_TIMESTAMP) + clock_gettime(CLOCK_REALTIME, &dev->d_rxtime); +#endif + if (dev->d_iob != NULL) { buf = dev->d_buf; diff --git a/net/devif/ipv6_input.c b/net/devif/ipv6_input.c index b29daea10d..3be7d875a9 100644 --- a/net/devif/ipv6_input.c +++ b/net/devif/ipv6_input.c @@ -609,6 +609,12 @@ int ipv6_input(FAR struct net_driver_s *dev) FAR uint8_t *buf; int ret; + /* Store reception timestamp if enabled and not provided by hardware. */ + +#if defined(CONFIG_NET_TIMESTAMP) && !defined(CONFIG_ARCH_HAVE_NETDEV_TIMESTAMP) + clock_gettime(CLOCK_REALTIME, &dev->d_rxtime); +#endif + if (dev->d_iob != NULL) { buf = dev->d_buf; diff --git a/net/inet/inet_sockif.c b/net/inet/inet_sockif.c index 8e6f8bd91c..668047ed40 100644 --- a/net/inet/inet_sockif.c +++ b/net/inet/inet_sockif.c @@ -747,6 +747,27 @@ static int inet_get_socketlevel_option(FAR struct socket *psock, int option, } #endif +#ifdef CONFIG_NET_TIMESTAMP + case SO_TIMESTAMP: + { + if (*value_len != sizeof(int)) + { + return -EINVAL; + } + + if (psock->s_type == SOCK_DGRAM) + { + FAR struct udp_conn_s *conn = psock->s_conn; + *(FAR int *)value = (conn->timestamp != 0); + } + else + { + return -ENOPROTOOPT; + } + } + break; +#endif + default: return -ENOPROTOOPT; } @@ -1028,6 +1049,36 @@ static int inet_set_socketlevel_option(FAR struct socket *psock, int option, break; #endif +#ifdef CONFIG_NET_TIMESTAMP + case SO_TIMESTAMP: /* Report receive timestamps as cmsg */ + { + if (value_len < sizeof(int)) + { + return -EINVAL; + } + + if (psock->s_type == SOCK_DGRAM) + { + net_lock(); + + /* For now the timestamp enable is just boolean. + * If SO_TIMESTAMPING support is added in future, it can be + * expanded to flags field for rx/tx timestamps. + */ + + FAR struct udp_conn_s *conn = psock->s_conn; + conn->timestamp = (*((FAR int *)value) != 0); + + net_unlock(); + } + else + { + return -ENOPROTOOPT; + } + } + break; + #endif + default: return -ENOPROTOOPT; } diff --git a/net/socket/Kconfig b/net/socket/Kconfig index 6cfd9f6a95..c05b90ad1c 100644 --- a/net/socket/Kconfig +++ b/net/socket/Kconfig @@ -74,9 +74,10 @@ config NET_SOLINGER config NET_TIMESTAMP bool "SO_TIMESTAMP socket option" default n - depends on NET_CAN + depends on NET_CAN || NET_ETHERNET ---help--- - Enable or disable support for the SO_TIMESTAMP socket option. Currently only tested & implemented in SocketCAN but should work on all sockets + Enable or disable support for the SO_TIMESTAMP socket option. + Supported on SocketCAN and Ethernet/UDP. config NET_BINDTODEVICE bool "SO_BINDTODEVICE socket option Bind-to-device support" diff --git a/net/udp/udp.h b/net/udp/udp.h index 3832ec5a0a..8b8aa4bfe1 100644 --- a/net/udp/udp.h +++ b/net/udp/udp.h @@ -156,6 +156,10 @@ struct udp_conn_s */ struct udp_poll_s pollinfo[CONFIG_NET_UDP_NPOLLWAITERS]; + +#ifdef CONFIG_NET_TIMESTAMP + int timestamp; /* Nonzero when SO_TIMESTAMP is enabled */ +#endif }; /* This structure supports UDP write buffering. It is simply a container diff --git a/net/udp/udp_callback.c b/net/udp/udp_callback.c index ee35ba100f..ff417b5d18 100644 --- a/net/udp/udp_callback.c +++ b/net/udp/udp_callback.c @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -153,11 +154,26 @@ static uint16_t udp_datahandler(FAR struct net_driver_s *dev, #endif /* CONFIG_NET_IPv4 */ /* Copy the meta info into the I/O buffer chain, just before data. - * Layout: |datalen|ifindex|src_addr_size|src_addr|data| + * Layout: |datalen|ifindex|src_addr_size|src_addr|[timestamp]|data| */ offset = (dev->d_appdata - iob->io_data) - iob->io_offset; +#ifdef CONFIG_NET_TIMESTAMP + /* Store timestamp while packet is being queued. + * This is done unconditionally to avoid race condition when SO_TIMESTAMP + * gets enabled after packet is received but before it is read. + */ + + offset -= sizeof(struct timespec); + ret = iob_trycopyin(iob, (FAR const uint8_t *)&dev->d_rxtime, + sizeof(struct timespec), offset, true); + if (ret < 0) + { + goto errout; + } +#endif + offset -= src_addr_size; ret = iob_trycopyin(iob, src_addr, src_addr_size, offset, true); if (ret < 0) diff --git a/net/udp/udp_recvfrom.c b/net/udp/udp_recvfrom.c index a85b299ad2..1d19a535b5 100644 --- a/net/udp/udp_recvfrom.c +++ b/net/udp/udp_recvfrom.c @@ -30,6 +30,7 @@ #include #include +#include #include #include #include @@ -63,6 +64,19 @@ struct udp_recvfrom_s * Private Functions ****************************************************************************/ +#ifdef CONFIG_NET_TIMESTAMP +static void udp_store_cmsg_timestamp(FAR struct udp_recvfrom_s *pstate, + FAR struct timespec *timestamp) +{ + FAR struct msghdr *msg = pstate->ir_msg; + struct timeval tv; + + TIMESPEC_TO_TIMEVAL(&tv, timestamp); + cmsg_append(msg, SOL_SOCKET, SO_TIMESTAMP, + &tv, sizeof(struct timeval)); +} +#endif + #ifdef CONFIG_NET_SOCKOPTS static void udp_recvpktinfo(FAR struct udp_recvfrom_s *pstate, FAR void *srcaddr, uint8_t ifindex) @@ -178,7 +192,7 @@ static inline void udp_readahead(struct udp_recvfrom_s *pstate) #endif /* Unflatten saved connection information - * Layout: |datalen|ifindex|src_addr_size|src_addr|data| + * Layout: |datalen|ifindex|src_addr_size|src_addr|[timestamp]|data| */ recvlen = iob_copyout((FAR uint8_t *)&datalen, iob, @@ -202,6 +216,22 @@ static inline void udp_readahead(struct udp_recvfrom_s *pstate) offset += src_addr_size; DEBUGASSERT(recvlen == src_addr_size); +#ifdef CONFIG_NET_TIMESTAMP + /* Unpack stored timestamp if SO_TIMESTAMP socket option is enabled */ + + if (conn->timestamp) + { + struct timespec timestamp; + recvlen = iob_copyout((FAR uint8_t *)×tamp, iob, + sizeof(struct timespec), offset); + DEBUGASSERT(recvlen == sizeof(struct timespec)); + + udp_store_cmsg_timestamp(pstate, ×tamp); + } + + offset += sizeof(struct timespec); +#endif + /* Copy to user */ recvlen = iob_copyout(pstate->ir_msg->msg_iov->iov_base, iob, @@ -434,6 +464,15 @@ static uint16_t udp_eventhandler(FAR struct net_driver_s *dev, else if ((flags & UDP_NEWDATA) != 0) { + /* Save packet timestamp, if requested */ + +#ifdef CONFIG_NET_TIMESTAMP + if (pstate->ir_conn->timestamp) + { + udp_store_cmsg_timestamp(pstate, &dev->d_rxtime); + } +#endif + /* Save the sender's address in the caller's 'from' location */ udp_sender(dev, pstate);