[PATCH v5 net-next 2/3] netdevsim: Implement basic ptp support

Maciek Machnikowski
2026-05-03T07:48:30Z

Add support for virtual timestamping inside the netdevsim driver.
The implementation uses two attached ptp_mock clocks, reads the timestamps
of the ones attached either to the netdevsim or its peer and returns
timestamps using standard timestamps APIs.

This implementation enables running ptp4l on netdevsim adapters and
introduces a new ptp selftest.

Co-developed-by: Milena Olech <milena.olech@intel.com>
Signed-off-by: Milena Olech <milena.olech@intel.com>
Signed-off-by: Maciek Machnikowski <maciek@machnikowski.net>
---
drivers/net/netdevsim/ethtool.c | 11 ++++
drivers/net/netdevsim/netdev.c | 91 +++++++++++++++++++++++++++++++
drivers/net/netdevsim/netdevsim.h | 1 +
3 files changed, 103 insertions(+)

diff --git a/drivers/net/netdevsim/ethtool.c b/drivers/net/netdevsim/ethtool.c
index 36a201533aae..5b709033cc5f 100644
--- a/drivers/net/netdevsim/ethtool.c
+++ b/drivers/net/netdevsim/ethtool.c
@@ -200,7 +200,18 @@ static int nsim_get_ts_info(struct net_device *dev,
{
struct netdevsim *ns = netdev_priv(dev);

+ ethtool_op_get_ts_info(dev, info);
+
info->phc_index = mock_phc_index(ns->phc);
+ if (info->phc_index < 0)
+ return 0;
+
+ info->so_timestamping |= SOF_TIMESTAMPING_TX_HARDWARE |
+ SOF_TIMESTAMPING_RX_HARDWARE |
+ SOF_TIMESTAMPING_RAW_HARDWARE;
+
+ info->tx_types = BIT(HWTSTAMP_TX_OFF) | BIT(HWTSTAMP_TX_ON);
+ info->rx_filters = BIT(HWTSTAMP_FILTER_NONE) | BIT(HWTSTAMP_FILTER_ALL);

return 0;
}
diff --git a/drivers/net/netdevsim/netdev.c b/drivers/net/netdevsim/netdev.c
index a05af192caf3..66493c94df94 100644
--- a/drivers/net/netdevsim/netdev.c
+++ b/drivers/net/netdevsim/netdev.c
@@ -30,6 +30,8 @@
#include <net/rtnetlink.h>
#include <net/udp_tunnel.h>
#include <net/busy_poll.h>
+#include <linux/ptp_clock_kernel.h>
+#include <linux/timecounter.h>

#include "netdevsim.h"

@@ -122,7 +124,11 @@ static int nsim_forward_skb(struct net_device *tx_dev,

static netdev_tx_t nsim_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
+ struct skb_shared_hwtstamps shhwtstamps = {};
struct netdevsim *ns = netdev_priv(dev);
+ struct ptp_clock_info *ptp_info;
+ struct timespec64 tx_ts, rx_ts;
+ struct sk_buff *skb_orig = skb;
struct skb_ext *psp_ext = NULL;
struct net_device *peer_dev;
unsigned int len = skb->len;
@@ -164,6 +170,36 @@ static netdev_tx_t nsim_start_xmit(struct sk_buff *skb, struct net_device *dev)
skb_linearize(skb);

skb_tx_timestamp(skb);
+
+ /* Generate RX timestamp using the peer's PHC if RX timestamping is enabled */
+ if (peer_ns->tstamp_config.rx_filter != HWTSTAMP_FILTER_NONE) {
+ ptp_info = mock_phc_get_ptp_info(peer_ns->phc);
+ ptp_info->gettime64(ptp_info, &rx_ts);
+ }
+
+ /* If TX hardware timestamping is enabled, generate and attach a TX timestamp */
+ if (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP &&
+ peer_ns->tstamp_config.tx_type == HWTSTAMP_TX_ON) {
+ ptp_info = mock_phc_get_ptp_info(ns->phc);
+ ptp_info->gettime64(ptp_info, &tx_ts);
+
+ /* Create a copy of the SKB to forward to peer and prevent
+ * from reporting incorrect TX timestamp when skb_hwtstamps is set.
+ */
+ skb = skb_copy(skb_orig, GFP_ATOMIC);
+ if (skb) {
+ shhwtstamps.hwtstamp = timespec64_to_ktime(tx_ts);
+ skb_tstamp_tx(skb_orig, &shhwtstamps);
+ consume_skb(skb_orig);
+ } else {
+ skb = skb_orig;
+ }
+ }
+
+ /* set the rx timestamp to the skb */
+ if (peer_ns->tstamp_config.rx_filter != HWTSTAMP_FILTER_NONE)
+ skb_hwtstamps(skb)->hwtstamp = timespec64_to_ktime(rx_ts);
+
if (unlikely(nsim_forward_skb(dev, peer_dev,
skb, rq, psp_ext) == NET_RX_DROP))
goto out_drop_cnt;
@@ -185,6 +221,59 @@ static netdev_tx_t nsim_start_xmit(struct sk_buff *skb, struct net_device *dev)
return NETDEV_TX_OK;
}

+static int nsim_set_ts_config(struct net_device *netdev,
+ struct kernel_hwtstamp_config *config,
+ struct netlink_ext_ack *extack)
+{
+ struct netdevsim *ns = netdev_priv(netdev);
+
+ if (!ns->phc)
+ return -EOPNOTSUPP;
+
+ switch (config->tx_type) {
+ case HWTSTAMP_TX_OFF:
+ ns->tstamp_config.tx_type = HWTSTAMP_TX_OFF;
+ break;
+ case HWTSTAMP_TX_ON:
+ ns->tstamp_config.tx_type = HWTSTAMP_TX_ON;
+ break;
+ default:
+ return -ERANGE;
+ }
+
+ switch (config->rx_filter) {
+ case HWTSTAMP_FILTER_NONE:
+ ns->tstamp_config.rx_filter = HWTSTAMP_FILTER_NONE;
+ break;
+ case HWTSTAMP_FILTER_PTP_V1_L4_EVENT:
+ case HWTSTAMP_FILTER_PTP_V1_L4_SYNC:
+ case HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ:
+ case HWTSTAMP_FILTER_PTP_V2_EVENT:
+ case HWTSTAMP_FILTER_PTP_V2_L4_EVENT:
+ case HWTSTAMP_FILTER_PTP_V2_SYNC:
+ case HWTSTAMP_FILTER_PTP_V2_L4_SYNC:
+ case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ:
+ case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ:
+ case HWTSTAMP_FILTER_NTP_ALL:
+ case HWTSTAMP_FILTER_ALL:
+ ns->tstamp_config.rx_filter = HWTSTAMP_FILTER_ALL;
+ break;
+ default:
+ return -ERANGE;
+ }
+
+ return 0;
+}
+
+static int nsim_get_ts_config(struct net_device *netdev,
+ struct kernel_hwtstamp_config *config)
+{
+ struct netdevsim *ns = netdev_priv(netdev);
+
+ *config = ns->tstamp_config;
+ return 0;
+}
+
static void nsim_set_rx_mode(struct net_device *dev,
struct netdev_hw_addr_list *uc,
struct netdev_hw_addr_list *mc)
@@ -646,6 +735,8 @@ static const struct net_device_ops nsim_netdev_ops = {
.ndo_vlan_rx_add_vid = nsim_vlan_rx_add_vid,
.ndo_vlan_rx_kill_vid = nsim_vlan_rx_kill_vid,
.net_shaper_ops = &nsim_shaper_ops,
+ .ndo_hwtstamp_get = nsim_get_ts_config,
+ .ndo_hwtstamp_set = nsim_set_ts_config,
};

static const struct net_device_ops nsim_vf_netdev_ops = {
diff --git a/drivers/net/netdevsim/netdevsim.h b/drivers/net/netdevsim/netdevsim.h
index 7e129dddbbe7..dade677448bc 100644
--- a/drivers/net/netdevsim/netdevsim.h
+++ b/drivers/net/netdevsim/netdevsim.h
@@ -109,6 +109,7 @@ struct netdevsim {
struct net_device *netdev;
struct nsim_dev *nsim_dev;
struct nsim_dev_port *nsim_dev_port;
+ struct kernel_hwtstamp_config tstamp_config;
struct mock_phc *phc;
struct nsim_rq **rq;

--
2.53.0