* [PATCH v2 net-next 1/3] ptp_mock: Expose ptp_clock_info to external drivers
2026-02-22 9:41 [PATCH v2 net-next 0/3] Implement PTP support in netdevsim Maciek Machnikowski
@ 2026-02-22 9:41 ` Maciek Machnikowski
2026-02-23 16:36 ` Jakub Kicinski
2026-02-22 9:41 ` [PATCH v2 net-next 2/3] netdevsim: Implement basic ptp support Maciek Machnikowski
2026-02-22 9:41 ` [PATCH v2 net-next 3/3] selftests:net: Implement ptp4l sync test using netdevsim Maciek Machnikowski
2 siblings, 1 reply; 7+ messages in thread
From: Maciek Machnikowski @ 2026-02-22 9:41 UTC (permalink / raw)
To: netdev
Cc: kuba, maciek, richardcochran, milena.olech, willemdebruijn.kernel,
andrew, vadim.fedorenko
Allow exposing the ptp_clock_info of the ptp_mock to the external drivers.
Convert spinlocks to SLBH to allow gettime to be called from the netdevsim.
This is a prerequisite for implementing ptp support on netdevsim.
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/ptp/ptp_mock.c | 22 ++++++++++++++--------
include/linux/ptp_mock.h | 5 +++++
2 files changed, 19 insertions(+), 8 deletions(-)
diff --git a/drivers/ptp/ptp_mock.c b/drivers/ptp/ptp_mock.c
index bbd14ce24..84cfee4aa 100644
--- a/drivers/ptp/ptp_mock.c
+++ b/drivers/ptp/ptp_mock.c
@@ -54,10 +54,10 @@ static int mock_phc_adjfine(struct ptp_clock_info *info, long scaled_ppm)
adj = (s64)scaled_ppm << MOCK_PHC_FADJ_SHIFT;
adj = div_s64(adj, MOCK_PHC_FADJ_DENOMINATOR);
- spin_lock(&phc->lock);
+ spin_lock_bh(&phc->lock);
timecounter_read(&phc->tc);
phc->cc.mult = MOCK_PHC_CC_MULT + adj;
- spin_unlock(&phc->lock);
+ spin_unlock_bh(&phc->lock);
return 0;
}
@@ -66,9 +66,9 @@ static int mock_phc_adjtime(struct ptp_clock_info *info, s64 delta)
{
struct mock_phc *phc = info_to_phc(info);
- spin_lock(&phc->lock);
+ spin_lock_bh(&phc->lock);
timecounter_adjtime(&phc->tc, delta);
- spin_unlock(&phc->lock);
+ spin_unlock_bh(&phc->lock);
return 0;
}
@@ -79,9 +79,9 @@ static int mock_phc_settime64(struct ptp_clock_info *info,
struct mock_phc *phc = info_to_phc(info);
u64 ns = timespec64_to_ns(ts);
- spin_lock(&phc->lock);
+ spin_lock_bh(&phc->lock);
timecounter_init(&phc->tc, &phc->cc, ns);
- spin_unlock(&phc->lock);
+ spin_unlock_bh(&phc->lock);
return 0;
}
@@ -91,9 +91,9 @@ static int mock_phc_gettime64(struct ptp_clock_info *info, struct timespec64 *ts
struct mock_phc *phc = info_to_phc(info);
u64 ns;
- spin_lock(&phc->lock);
+ spin_lock_bh(&phc->lock);
ns = timecounter_read(&phc->tc);
- spin_unlock(&phc->lock);
+ spin_unlock_bh(&phc->lock);
*ts = ns_to_timespec64(ns);
@@ -171,5 +171,11 @@ void mock_phc_destroy(struct mock_phc *phc)
}
EXPORT_SYMBOL_GPL(mock_phc_destroy);
+struct ptp_clock_info *mock_phc_get_ptp_info(struct mock_phc *phc)
+{
+ return &phc->info;
+}
+EXPORT_SYMBOL_GPL(mock_phc_get_ptp_info);
+
MODULE_DESCRIPTION("Mock-up PTP Hardware Clock driver");
MODULE_LICENSE("GPL");
diff --git a/include/linux/ptp_mock.h b/include/linux/ptp_mock.h
index 72eb40103..e33188dec 100644
--- a/include/linux/ptp_mock.h
+++ b/include/linux/ptp_mock.h
@@ -16,6 +16,7 @@ struct mock_phc;
struct mock_phc *mock_phc_create(struct device *dev);
void mock_phc_destroy(struct mock_phc *phc);
int mock_phc_index(struct mock_phc *phc);
+struct ptp_clock_info *mock_phc_get_ptp_info(struct mock_phc *phc);
#else
@@ -33,6 +34,10 @@ static inline int mock_phc_index(struct mock_phc *phc)
return -1;
}
+static inline struct ptp_clock_info *mock_phc_get_ptp_info(struct mock_phc *phc)
+{
+ return NULL;
+}
#endif
#endif /* _PTP_MOCK_H_ */
--
2.53.0
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH v2 net-next 2/3] netdevsim: Implement basic ptp support
2026-02-22 9:41 [PATCH v2 net-next 0/3] Implement PTP support in netdevsim Maciek Machnikowski
2026-02-22 9:41 ` [PATCH v2 net-next 1/3] ptp_mock: Expose ptp_clock_info to external drivers Maciek Machnikowski
@ 2026-02-22 9:41 ` Maciek Machnikowski
2026-02-22 9:41 ` [PATCH v2 net-next 3/3] selftests:net: Implement ptp4l sync test using netdevsim Maciek Machnikowski
2 siblings, 0 replies; 7+ messages in thread
From: Maciek Machnikowski @ 2026-02-22 9:41 UTC (permalink / raw)
To: netdev
Cc: kuba, maciek, richardcochran, milena.olech, willemdebruijn.kernel,
andrew, vadim.fedorenko
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>
Co-authored-by: Cursor <cursoragent@cursor.com>
---
drivers/net/netdevsim/ethtool.c | 11 ++++
drivers/net/netdevsim/netdev.c | 90 +++++++++++++++++++++++++++++++
drivers/net/netdevsim/netdevsim.h | 1 +
3 files changed, 102 insertions(+)
diff --git a/drivers/net/netdevsim/ethtool.c b/drivers/net/netdevsim/ethtool.c
index 36a201533..5b709033c 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 6285fbefe..b1161c1ca 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"
@@ -119,12 +121,17 @@ 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;
struct netdevsim *peer_ns;
struct netdev_config *cfg;
+ bool gen_tx_tstamp = false;
struct nsim_rq *rq;
int rxq;
int dr;
@@ -161,6 +168,27 @@ static netdev_tx_t nsim_start_xmit(struct sk_buff *skb, struct net_device *dev)
skb_linearize(skb);
skb_tx_timestamp(skb);
+ gen_tx_tstamp = skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP;
+ if (gen_tx_tstamp) {
+ ptp_info = mock_phc_get_ptp_info(ns->phc);
+
+ /* Create a copy of tx skb to keep the tx reference */
+ skb_orig = skb;
+ skb = skb_copy(skb_orig, GFP_ATOMIC);
+ skb_shinfo(skb_orig)->tx_flags |= SKBTX_IN_PROGRESS;
+
+ /* Timestamp as late as possible */
+ if (ptp_info)
+ ptp_info->gettime64(ptp_info, &tx_ts);
+ }
+
+ /* Generate Rx tstamp based on the peer clock */
+ ptp_info = mock_phc_get_ptp_info(peer_ns->phc);
+ if (ptp_info) {
+ ptp_info->gettime64(ptp_info, &rx_ts);
+ 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;
@@ -168,6 +196,13 @@ static netdev_tx_t nsim_start_xmit(struct sk_buff *skb, struct net_device *dev)
if (!hrtimer_active(&rq->napi_timer))
hrtimer_start(&rq->napi_timer, us_to_ktime(5), HRTIMER_MODE_REL);
+ /* only timestamp the outbound packet if user requested it */
+ if (gen_tx_tstamp) {
+ shhwtstamps.hwtstamp = timespec64_to_ktime(tx_ts);
+ skb_tstamp_tx(skb_orig, &shhwtstamps);
+ dev_kfree_skb_any(skb_orig);
+ }
+
rcu_read_unlock();
dev_dstats_tx_add(dev, len);
return NETDEV_TX_OK;
@@ -182,6 +217,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)
{
}
@@ -660,6 +748,8 @@ static const struct net_device_ops nsim_netdev_ops = {
.ndo_open = nsim_open,
.ndo_stop = nsim_stop,
.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 f767fc8a7..6d3e8700c 100644
--- a/drivers/net/netdevsim/netdevsim.h
+++ b/drivers/net/netdevsim/netdevsim.h
@@ -103,6 +103,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
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH v2 net-next 3/3] selftests:net: Implement ptp4l sync test using netdevsim
2026-02-22 9:41 [PATCH v2 net-next 0/3] Implement PTP support in netdevsim Maciek Machnikowski
2026-02-22 9:41 ` [PATCH v2 net-next 1/3] ptp_mock: Expose ptp_clock_info to external drivers Maciek Machnikowski
2026-02-22 9:41 ` [PATCH v2 net-next 2/3] netdevsim: Implement basic ptp support Maciek Machnikowski
@ 2026-02-22 9:41 ` Maciek Machnikowski
2026-02-24 10:54 ` Simon Horman
2 siblings, 1 reply; 7+ messages in thread
From: Maciek Machnikowski @ 2026-02-22 9:41 UTC (permalink / raw)
To: netdev
Cc: kuba, maciek, richardcochran, milena.olech, willemdebruijn.kernel,
andrew, vadim.fedorenko
Add PTP synchronization test using ptp4l and netdevsim.
The test creates two netdevsim adapters, links them together
and runs the ptp4l leader and ptp4l follower on two ends
of the netdevsim link and waits for the follower to report the
synchronized state (s2) in its output log.
This implementation runs the test runs over IPv4 link.
Signed-off-by: Maciek Machnikowski <maciek@machnikowski.net>
---
tools/testing/selftests/net/ptp.sh | 93 ++++++++++++++++++++++++++++++
1 file changed, 93 insertions(+)
create mode 100755 tools/testing/selftests/net/ptp.sh
diff --git a/tools/testing/selftests/net/ptp.sh b/tools/testing/selftests/net/ptp.sh
new file mode 100755
index 000000000..8b1604b96
--- /dev/null
+++ b/tools/testing/selftests/net/ptp.sh
@@ -0,0 +1,93 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0-only
+source lib.sh
+
+NSIM_DEV_1_ID=$((256 + RANDOM % 256))
+NSIM_DEV_2_ID=$((512 + RANDOM % 256))
+
+NSIM_DEV_SYS_LINK=/sys/bus/netdevsim/link_device
+NSIM_DEV_SYS_UNLINK=/sys/bus/netdevsim/unlink_device
+PTP4L_SYNC_TIMEOUT=20
+
+setup_netdevsim()
+{
+ set -e
+ ip netns add nssv
+ ip netns add nscl
+
+ NSIM_DEV_1_NAME=$(create_netdevsim $NSIM_DEV_1_ID nssv)
+ NSIM_DEV_2_NAME=$(create_netdevsim $NSIM_DEV_2_ID nscl)
+
+ ip netns exec nssv ip addr add '192.168.1.1/24' dev $NSIM_DEV_1_NAME
+ ip netns exec nscl ip addr add '192.168.1.2/24' dev $NSIM_DEV_2_NAME
+
+ set +e
+}
+
+cleanup()
+{
+ [ -n "${PTP4L_LEADER_PID-}" ] && {
+ kill $PTP4L_LEADER_PID $PTP4L_FOLLOWER_PID 2>/dev/null
+ wait $PTP4L_LEADER_PID $PTP4L_FOLLOWER_PID 2>/dev/null
+ rm -f "$PTP4L_LEADER_LOG" "$PTP4L_FOLLOWER_LOG"
+ }
+
+ [ -n "${NSIM_DEV_1_FD-}" ] && [ -n "${NSIM_DEV_1_IFIDX-}" ] && \
+ echo "$NSIM_DEV_1_FD:$NSIM_DEV_1_IFIDX" > "$NSIM_DEV_SYS_UNLINK" 2>/dev/null
+ cleanup_netdevsim "${NSIM_DEV_2_ID-}" 2>/dev/null
+ cleanup_netdevsim "${NSIM_DEV_1_ID-}" 2>/dev/null
+
+ ip netns del nscl 2>/dev/null
+ ip netns del nssv 2>/dev/null
+
+ modprobe -r netdevsim 2>/dev/null
+}
+
+###
+### Code start
+###
+if [ ! -x "$(command -v ptp4l)" ]; then
+ echo "ptp4l command not found. Skipping PTP sync test"
+ exit 4
+fi
+
+trap cleanup EXIT
+
+setup_netdevsim
+
+# Link netdevsim1 with netdevsim2
+NSIM_DEV_1_FD=$((256 + RANDOM % 256))
+exec {NSIM_DEV_1_FD}</var/run/netns/nssv
+NSIM_DEV_1_IFIDX=$(ip netns exec nssv cat /sys/class/net/$NSIM_DEV_1_NAME/ifindex)
+
+NSIM_DEV_2_FD=$((512 + RANDOM % 256))
+exec {NSIM_DEV_2_FD}</var/run/netns/nscl
+NSIM_DEV_2_IFIDX=$(ip netns exec nscl cat /sys/class/net/$NSIM_DEV_2_NAME/ifindex)
+
+if ! echo "$NSIM_DEV_1_FD:$NSIM_DEV_1_IFIDX $NSIM_DEV_2_FD:$NSIM_DEV_2_IFIDX" > "$NSIM_DEV_SYS_LINK"; then
+ echo "linking netdevsim1 with netdevsim2 failed"
+ exit 1
+fi
+
+# PTP synchronization test: run ptp4l leader in nssv and follower in nscl,
+# then parse follower output to verify they synchronized (servo state s2 = locked).
+PTP4L_LEADER_LOG=$(mktemp)
+PTP4L_FOLLOWER_LOG=$(mktemp)
+ip netns exec nssv ptp4l -i "$NSIM_DEV_1_NAME" -m -4 -P \
+ > "$PTP4L_LEADER_LOG" 2>&1 &
+PTP4L_LEADER_PID=$!
+
+ip netns exec nscl ptp4l -i "$NSIM_DEV_2_NAME" -s -m -4 -P \
+ > "$PTP4L_FOLLOWER_LOG" 2>&1 &
+PTP4L_FOLLOWER_PID=$!
+
+
+for _ in $(seq 1 $PTP4L_SYNC_TIMEOUT); do
+ if grep -q ' s2 ' "$PTP4L_FOLLOWER_LOG" 2>/dev/null; then
+ exit 0
+ fi
+ sleep 1
+done
+
+echo "ptp4l follower did not reach locked state (s2) within ${PTP4L_SYNC_TIMEOUT}s"
+exit 1
\ No newline at end of file
--
2.53.0
^ permalink raw reply related [flat|nested] 7+ messages in thread