* [PATCH net-next v5 1/9] net: ethtool: tsconfig: allow hardware timestamping without a PHC
2026-11-12 11:19 [PATCH net-next v5 0/9] virtio-net: add receive timestamp support Steffen Trumtrar
@ 2026-11-12 11:19 ` Steffen Trumtrar
2026-11-12 11:19 ` [PATCH net-next v5 2/9] virtio-net: zero the whole tx header Steffen Trumtrar
` (8 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Steffen Trumtrar @ 2026-11-12 11:19 UTC (permalink / raw)
To: Michael S. Tsirkin, Xuan Zhuo, David S. Miller, Jakub Kicinski,
Paolo Abeni, Richard Cochran, Willem de Bruijn, Andrew Lunn,
Eugenio Pérez, Jason Wang, Alexei Starovoitov,
Daniel Borkmann, Jesper Dangaard Brouer, John Fastabend,
Stanislav Fomichev, Eric Dumazet, Andrew Lunn, Simon Horman,
Kory Maincent, Liang Chen, Heng Qi
Cc: virtualization, netdev, linux-kernel, bpf, kvm, kernel, Steffen Trumtrar
A driver can do hardware timestamping without a PTP hardware clock; its
get_ts_info() then reports hardware timestamping with phc_index -1.
ngbe and txgbe (libwx) do so when no PTP clock is registered, e.g. with
CONFIG_PTP_1588_CLOCK=n, and dpaa without a ptimer-handle.
SIOCGHWTSTAMP and SIOCSHWTSTAMP work for such a device, but
tsconfig_prepare_data() refuses it with -ENODEV:
- TSCONFIG_GET fails.
- TSCONFIG_SET passes the new config to ndo_hwtstamp_set() first and
fails only when building the reply, so user space gets an error
although the config was applied.
- In a dump only -EOPNOTSUPP is skipped; the error ends the dump, and
the devices after this one are not listed.
The reply already leaves out ETHTOOL_A_TSCONFIG_HWTSTAMP_PROVIDER when
the index is -1. Let tsconfig_prepare_data() take that path instead of
failing.
Fixes: 6e9e2eed4f39 ("net: ethtool: Add support for tsconfig command to get/set hwtstamp config")
Signed-off-by: Steffen Trumtrar <s.trumtrar@pengutronix.de>
---
net/ethtool/tsconfig.c | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/net/ethtool/tsconfig.c b/net/ethtool/tsconfig.c
index 2db0e7ba8b9f5..75f9b319aa310 100644
--- a/net/ethtool/tsconfig.c
+++ b/net/ethtool/tsconfig.c
@@ -73,11 +73,7 @@ static int tsconfig_prepare_data(const struct ethnl_req_info *req_base,
if (ret)
goto out;
- if (ts_info.phc_index == -1) {
- ret = -ENODEV;
- goto out;
- }
-
+ /* -1 without a PHC: the reply leaves out the provider */
data->hwprov_desc.index = ts_info.phc_index;
data->hwprov_desc.qualifier = ts_info.phc_qualifier;
}
--
2.56.0
^ permalink raw reply [flat|nested] 11+ messages in thread* [PATCH net-next v5 2/9] virtio-net: zero the whole tx header
2026-11-12 11:19 [PATCH net-next v5 0/9] virtio-net: add receive timestamp support Steffen Trumtrar
2026-11-12 11:19 ` [PATCH net-next v5 1/9] net: ethtool: tsconfig: allow hardware timestamping without a PHC Steffen Trumtrar
@ 2026-11-12 11:19 ` Steffen Trumtrar
2026-11-12 11:19 ` [PATCH net-next v5 3/9] virtio-net: copy the XDP rx hash before the program runs Steffen Trumtrar
` (7 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Steffen Trumtrar @ 2026-11-12 11:19 UTC (permalink / raw)
To: Michael S. Tsirkin, Xuan Zhuo, David S. Miller, Jakub Kicinski,
Paolo Abeni, Richard Cochran, Willem de Bruijn, Andrew Lunn,
Eugenio Pérez, Jason Wang, Alexei Starovoitov,
Daniel Borkmann, Jesper Dangaard Brouer, John Fastabend,
Stanislav Fomichev, Eric Dumazet, Andrew Lunn, Simon Horman,
Kory Maincent, Liang Chen, Heng Qi
Cc: virtualization, netdev, linux-kernel, bpf, kvm, kernel, Steffen Trumtrar
xmit_skb() builds the virtio-net header either in skb->cb or in the
headroom in front of the packet. virtio_net_hdr_tnl_from_skb() only
clears struct virtio_net_hdr (10 bytes) and, for tunnel GSO, the hash
fields; num_buffers is cleared only with mergeable rx buffers. With a
larger negotiated header the remaining bytes go to the device as they
were in skb->cb or the headroom:
- num_buffers with VIRTIO_F_VERSION_1 but without
VIRTIO_NET_F_MRG_RXBUF (header size 12),
- hash_value, hash_report and padding with
VIRTIO_NET_F_HASH_REPORT (header size 20),
- additionally outer_th_offset and inner_nh_offset for packets
without tunnel GSO if a UDP tunnel GSO feature was negotiated
(header size 24).
Clear the whole header before filling it in. This also covers
num_buffers, so drop the separate assignment.
The helpers still clear struct virtio_net_hdr and, for tunnel GSO, the
hash fields themselves; that is now redundant here, but they are
shared with tun, tap, af_packet and the UML vector driver and stay as
they are.
Signed-off-by: Steffen Trumtrar <s.trumtrar@pengutronix.de>
---
drivers/net/virtio_net.c | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/drivers/net/virtio_net.c b/drivers/net/virtio_net.c
index dd2c41ef6fbe7..4c740b403ad1e 100644
--- a/drivers/net/virtio_net.c
+++ b/drivers/net/virtio_net.c
@@ -3326,14 +3326,16 @@ static int xmit_skb(struct send_queue *sq, struct sk_buff *skb, bool orphan)
else
hdr = &skb_vnet_common_hdr(skb)->tnl_hdr;
+ /* Fields the helper below does not fill in must not carry stale
+ * skb->cb or headroom bytes to the device.
+ */
+ memset(hdr, 0, hdr_len);
+
if (virtio_net_hdr_tnl_from_skb(skb, hdr, vi->tx_tnl,
virtio_is_little_endian(vi->vdev), 0,
false, feature_hdrlen))
return -EPROTO;
- if (vi->mergeable_rx_bufs)
- hdr->hash_hdr.hdr.num_buffers = 0;
-
sg_init_table(sq->sg, skb_shinfo(skb)->nr_frags + (can_push ? 1 : 2));
if (can_push) {
__skb_push(skb, hdr_len);
--
2.56.0
^ permalink raw reply [flat|nested] 11+ messages in thread* [PATCH net-next v5 3/9] virtio-net: copy the XDP rx hash before the program runs
2026-11-12 11:19 [PATCH net-next v5 0/9] virtio-net: add receive timestamp support Steffen Trumtrar
2026-11-12 11:19 ` [PATCH net-next v5 1/9] net: ethtool: tsconfig: allow hardware timestamping without a PHC Steffen Trumtrar
2026-11-12 11:19 ` [PATCH net-next v5 2/9] virtio-net: zero the whole tx header Steffen Trumtrar
@ 2026-11-12 11:19 ` Steffen Trumtrar
2026-11-12 11:19 ` [PATCH net-next v5 4/9] virtio-net: move virtio_net_common_hdr to virtio_net.h Steffen Trumtrar
` (6 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Steffen Trumtrar @ 2026-11-12 11:19 UTC (permalink / raw)
To: Michael S. Tsirkin, Xuan Zhuo, David S. Miller, Jakub Kicinski,
Paolo Abeni, Richard Cochran, Willem de Bruijn, Andrew Lunn,
Eugenio Pérez, Jason Wang, Alexei Starovoitov,
Daniel Borkmann, Jesper Dangaard Brouer, John Fastabend,
Stanislav Fomichev, Eric Dumazet, Andrew Lunn, Simon Horman,
Kory Maincent, Liang Chen, Heng Qi
Cc: virtualization, netdev, linux-kernel, bpf, kvm, kernel, Steffen Trumtrar
virtnet_xdp_rx_hash() reads the hash from the vnet header at
xdp->data - vi->hdr_len when the XDP program calls
bpf_xdp_metadata_rx_hash(). That is only the header until the program
changes the buffer: bpf_xdp_adjust_meta() writes the metadata right in
front of xdp->data, over the header, and bpf_xdp_adjust_head() moves
xdp->data, so the callback then reads metadata or packet bytes.
Wrap struct xdp_buff in struct virtnet_xdp_buff, copy hash value and
report type into it in virtnet_xdp_handler() before
bpf_prog_run_xdp(), and let the callback read the copy. The small and
mergeable paths put the wrapper on the stack; for AF_XDP the fields are
in the cb of struct xdp_buff_xsk, checked with XSK_CHECK_PRIV_TYPE().
Move virtio_net_hash_value() up so the copy can use it.
Fixes: aa37f8916d20 ("virtio_net: Support RX hash XDP hint")
Signed-off-by: Steffen Trumtrar <s.trumtrar@pengutronix.de>
---
drivers/net/virtio_net.c | 86 ++++++++++++++++++++++++++++++++----------------
1 file changed, 58 insertions(+), 28 deletions(-)
diff --git a/drivers/net/virtio_net.c b/drivers/net/virtio_net.c
index 4c740b403ad1e..ffd7313663c18 100644
--- a/drivers/net/virtio_net.c
+++ b/drivers/net/virtio_net.c
@@ -491,6 +491,26 @@ struct padded_vnet_hdr {
char padding[12];
};
+/*
+ * The vnet header sits in the headroom in front of xdp->data, which the
+ * XDP program may overwrite with bpf_xdp_adjust_head() or
+ * bpf_xdp_adjust_meta(). What the metadata kfuncs report is copied from
+ * it before the program runs. For AF_XDP the fields behind xdp live in
+ * the cb of struct xdp_buff_xsk.
+ */
+struct virtnet_xdp_buff {
+ struct xdp_buff xdp;
+ u32 hash_value;
+ u16 hash_report;
+};
+
+static inline u32
+virtio_net_hash_value(const struct virtio_net_hdr_v1_hash *hdr_hash)
+{
+ return __le16_to_cpu(hdr_hash->hash_value_lo) |
+ (__le16_to_cpu(hdr_hash->hash_value_hi) << 16);
+}
+
struct virtio_net_common_hdr {
union {
struct virtio_net_hdr hdr;
@@ -1687,6 +1707,24 @@ static void put_xdp_frags(struct receive_queue *rq, struct xdp_buff *xdp)
}
}
+/* xdp must be embedded in struct virtnet_xdp_buff or struct xdp_buff_xsk */
+static void virtnet_xdp_save_hdr(const struct virtnet_info *vi,
+ struct xdp_buff *xdp)
+{
+ struct virtnet_xdp_buff *ctx = container_of(xdp, struct virtnet_xdp_buff, xdp);
+ const struct virtio_net_hdr_v1_hash *hdr_hash = xdp->data - vi->hdr_len;
+
+ XSK_CHECK_PRIV_TYPE(struct virtnet_xdp_buff);
+
+ if (vi->has_rss_hash_report) {
+ ctx->hash_value = virtio_net_hash_value(hdr_hash);
+ ctx->hash_report = __le16_to_cpu(hdr_hash->hash_report);
+ } else {
+ ctx->hash_value = 0;
+ ctx->hash_report = VIRTIO_NET_HASH_REPORT_NONE;
+ }
+}
+
static int virtnet_xdp_handler(struct bpf_prog *xdp_prog, struct xdp_buff *xdp,
struct net_device *dev,
unsigned int *xdp_xmit,
@@ -1696,6 +1734,8 @@ static int virtnet_xdp_handler(struct bpf_prog *xdp_prog, struct xdp_buff *xdp,
int err;
u32 act;
+ virtnet_xdp_save_hdr(netdev_priv(dev), xdp);
+
act = bpf_prog_run_xdp(xdp_prog, xdp);
u64_stats_inc(&stats->xdp_packets);
@@ -1867,7 +1907,8 @@ static struct sk_buff *receive_small_xdp(struct net_device *dev,
struct virtio_net_hdr_mrg_rxbuf *hdr = buf + header_offset;
struct page *page = virt_to_head_page(buf);
struct page *xdp_page;
- struct xdp_buff xdp;
+ struct virtnet_xdp_buff vxdp;
+ struct xdp_buff *xdp = &vxdp.xdp;
struct sk_buff *skb;
unsigned int metasize = 0;
u32 act;
@@ -1900,17 +1941,17 @@ static struct sk_buff *receive_small_xdp(struct net_device *dev,
page = xdp_page;
}
- xdp_init_buff(&xdp, buflen, &rq->xdp_rxq);
- xdp_prepare_buff(&xdp, buf + VIRTNET_RX_PAD + vi->hdr_len,
+ xdp_init_buff(xdp, buflen, &rq->xdp_rxq);
+ xdp_prepare_buff(xdp, buf + VIRTNET_RX_PAD + vi->hdr_len,
xdp_headroom, len, true);
- act = virtnet_xdp_handler(xdp_prog, &xdp, dev, xdp_xmit, stats);
+ act = virtnet_xdp_handler(xdp_prog, xdp, dev, xdp_xmit, stats);
switch (act) {
case XDP_PASS:
/* Recalculate length in case bpf program changed it */
- len = xdp.data_end - xdp.data;
- metasize = xdp.data - xdp.data_meta;
+ len = xdp->data_end - xdp->data;
+ metasize = xdp->data - xdp->data_meta;
break;
case XDP_TX:
@@ -1921,7 +1962,7 @@ static struct sk_buff *receive_small_xdp(struct net_device *dev,
goto err_xdp;
}
- skb = virtnet_build_skb(buf, buflen, xdp.data - buf, len);
+ skb = virtnet_build_skb(buf, buflen, xdp->data - buf, len);
if (unlikely(!skb)) {
u64_stats_inc(&stats->alloc_fail);
goto err;
@@ -2288,7 +2329,8 @@ static struct sk_buff *receive_mergeable_xdp(struct net_device *dev,
unsigned int xdp_frags_truesz = 0;
struct sk_buff *head_skb;
unsigned int frame_sz;
- struct xdp_buff xdp;
+ struct virtnet_xdp_buff vxdp;
+ struct xdp_buff *xdp = &vxdp.xdp;
void *data;
u32 act;
int err;
@@ -2298,16 +2340,16 @@ static struct sk_buff *receive_mergeable_xdp(struct net_device *dev,
if (unlikely(!data))
goto err_xdp;
- err = virtnet_build_xdp_buff_mrg(dev, vi, rq, &xdp, data, len, frame_sz,
+ err = virtnet_build_xdp_buff_mrg(dev, vi, rq, xdp, data, len, frame_sz,
&num_buf, &xdp_frags_truesz, stats);
if (unlikely(err))
goto err_xdp;
- act = virtnet_xdp_handler(xdp_prog, &xdp, dev, xdp_xmit, stats);
+ act = virtnet_xdp_handler(xdp_prog, xdp, dev, xdp_xmit, stats);
switch (act) {
case XDP_PASS:
- head_skb = build_skb_from_xdp_buff(dev, vi, &xdp, xdp_frags_truesz);
+ head_skb = build_skb_from_xdp_buff(dev, vi, xdp, xdp_frags_truesz);
if (unlikely(!head_skb)) {
u64_stats_inc(&stats->alloc_fail);
break;
@@ -2324,7 +2366,7 @@ static struct sk_buff *receive_mergeable_xdp(struct net_device *dev,
break;
}
- put_xdp_frags(rq, &xdp);
+ put_xdp_frags(rq, xdp);
err_xdp:
page_pool_put_page(rq->page_pool, page, -1, true);
@@ -2477,13 +2519,6 @@ static struct sk_buff *receive_mergeable(struct net_device *dev,
return NULL;
}
-static inline u32
-virtio_net_hash_value(const struct virtio_net_hdr_v1_hash *hdr_hash)
-{
- return __le16_to_cpu(hdr_hash->hash_value_lo) |
- (__le16_to_cpu(hdr_hash->hash_value_hi) << 16);
-}
-
static void virtio_skb_set_hash(const struct virtio_net_hdr_v1_hash *hdr_hash,
struct sk_buff *skb)
{
@@ -6760,23 +6795,18 @@ virtnet_xdp_rss_type[VIRTIO_NET_HASH_REPORT_MAX_TABLE] = {
static int virtnet_xdp_rx_hash(const struct xdp_md *_ctx, u32 *hash,
enum xdp_rss_hash_type *rss_type)
{
- const struct xdp_buff *xdp = (void *)_ctx;
- struct virtio_net_hdr_v1_hash *hdr_hash;
- struct virtnet_info *vi;
+ const struct virtnet_xdp_buff *ctx = (void *)_ctx;
u16 hash_report;
- if (!(xdp->rxq->dev->features & NETIF_F_RXHASH))
+ if (!(ctx->xdp.rxq->dev->features & NETIF_F_RXHASH))
return -ENODATA;
- vi = netdev_priv(xdp->rxq->dev);
- hdr_hash = (struct virtio_net_hdr_v1_hash *)(xdp->data - vi->hdr_len);
- hash_report = __le16_to_cpu(hdr_hash->hash_report);
-
+ hash_report = ctx->hash_report;
if (hash_report >= VIRTIO_NET_HASH_REPORT_MAX_TABLE)
hash_report = VIRTIO_NET_HASH_REPORT_NONE;
*rss_type = virtnet_xdp_rss_type[hash_report];
- *hash = virtio_net_hash_value(hdr_hash);
+ *hash = ctx->hash_value;
return 0;
}
--
2.56.0
^ permalink raw reply [flat|nested] 11+ messages in thread* [PATCH net-next v5 4/9] virtio-net: move virtio_net_common_hdr to virtio_net.h
2026-11-12 11:19 [PATCH net-next v5 0/9] virtio-net: add receive timestamp support Steffen Trumtrar
` (2 preceding siblings ...)
2026-11-12 11:19 ` [PATCH net-next v5 3/9] virtio-net: copy the XDP rx hash before the program runs Steffen Trumtrar
@ 2026-11-12 11:19 ` Steffen Trumtrar
2026-11-12 11:19 ` [PATCH net-next v5 5/9] virtio-net: support receive timestamp Steffen Trumtrar
` (5 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Steffen Trumtrar @ 2026-11-12 11:19 UTC (permalink / raw)
To: Michael S. Tsirkin, Xuan Zhuo, David S. Miller, Jakub Kicinski,
Paolo Abeni, Richard Cochran, Willem de Bruijn, Andrew Lunn,
Eugenio Pérez, Jason Wang, Alexei Starovoitov,
Daniel Borkmann, Jesper Dangaard Brouer, John Fastabend,
Stanislav Fomichev, Eric Dumazet, Andrew Lunn, Simon Horman,
Kory Maincent, Liang Chen, Heng Qi
Cc: virtualization, netdev, linux-kernel, bpf, kvm, kernel, Steffen Trumtrar
tun and vhost need to know the layout and size of the virtio-net
header as the driver uses it, e.g. to fill in fields behind
virtio_net_hdr_v1_hash_tunnel. Move struct virtio_net_common_hdr to
the kernel-internal header that they include already.
No functional change.
Signed-off-by: Steffen Trumtrar <s.trumtrar@pengutronix.de>
---
drivers/net/virtio_net.c | 9 ---------
include/linux/virtio_net.h | 9 +++++++++
2 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/drivers/net/virtio_net.c b/drivers/net/virtio_net.c
index ffd7313663c18..23e7f62a6ff64 100644
--- a/drivers/net/virtio_net.c
+++ b/drivers/net/virtio_net.c
@@ -511,15 +511,6 @@ virtio_net_hash_value(const struct virtio_net_hdr_v1_hash *hdr_hash)
(__le16_to_cpu(hdr_hash->hash_value_hi) << 16);
}
-struct virtio_net_common_hdr {
- union {
- struct virtio_net_hdr hdr;
- struct virtio_net_hdr_mrg_rxbuf mrg_hdr;
- struct virtio_net_hdr_v1_hash hash_v1_hdr;
- struct virtio_net_hdr_v1_hash_tunnel tnl_hdr;
- };
-};
-
static struct virtio_net_common_hdr xsk_hdr;
static void virtnet_sq_free_unused_buf(struct virtqueue *vq, void *buf);
diff --git a/include/linux/virtio_net.h b/include/linux/virtio_net.h
index d6466f96cdd00..a6de308b8f25e 100644
--- a/include/linux/virtio_net.h
+++ b/include/linux/virtio_net.h
@@ -10,6 +10,15 @@
#include <uapi/linux/tcp.h>
#include <uapi/linux/virtio_net.h>
+struct virtio_net_common_hdr {
+ union {
+ struct virtio_net_hdr hdr;
+ struct virtio_net_hdr_mrg_rxbuf mrg_hdr;
+ struct virtio_net_hdr_v1_hash hash_v1_hdr;
+ struct virtio_net_hdr_v1_hash_tunnel tnl_hdr;
+ };
+};
+
static inline bool virtio_net_hdr_match_proto(__be16 protocol, __u8 gso_type)
{
switch (gso_type & ~VIRTIO_NET_HDR_GSO_ECN) {
--
2.56.0
^ permalink raw reply [flat|nested] 11+ messages in thread* [PATCH net-next v5 5/9] virtio-net: support receive timestamp
2026-11-12 11:19 [PATCH net-next v5 0/9] virtio-net: add receive timestamp support Steffen Trumtrar
` (3 preceding siblings ...)
2026-11-12 11:19 ` [PATCH net-next v5 4/9] virtio-net: move virtio_net_common_hdr to virtio_net.h Steffen Trumtrar
@ 2026-11-12 11:19 ` Steffen Trumtrar
2026-11-12 11:19 ` [PATCH net-next v5 6/9] virtio-net: assert header sizes Steffen Trumtrar
` (4 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Steffen Trumtrar @ 2026-11-12 11:19 UTC (permalink / raw)
To: Michael S. Tsirkin, Xuan Zhuo, David S. Miller, Jakub Kicinski,
Paolo Abeni, Richard Cochran, Willem de Bruijn, Andrew Lunn,
Eugenio Pérez, Jason Wang, Alexei Starovoitov,
Daniel Borkmann, Jesper Dangaard Brouer, John Fastabend,
Stanislav Fomichev, Eric Dumazet, Andrew Lunn, Simon Horman,
Kory Maincent, Liang Chen, Heng Qi
Cc: virtualization, netdev, linux-kernel, bpf, kvm, kernel, Steffen Trumtrar
Add optional hardware rx timestamp offload for virtio-net.
Introduce the virtio feature VIRTIO_NET_F_RX_TSTAMP. If negotiated, the
virtio-net header grows by room for a 64 bit TAI timestamp (header
size 40 bytes), stored in struct virtio_net_common_hdr. The layout is
cumulative: the field is at the same offset whether or not the
features before it were negotiated. The fixed layout needs
VIRTIO_F_VERSION_1, so the feature is cleared without it.
Implement ndo_hwtstamp_get/set so the timestamping can be enabled
and disabled at runtime with SIOCSHWTSTAMP. The driver cannot filter
what the device timestamps, so any rx filter other than NONE becomes
ALL. The timestamp is read before the packet is handed to the XDP
program or the stack, and attached to the skb with skb_hwtstamps().
It is a raw TAI value; there is no PHC.
The header size is negotiated per device and also applies to
transmitted packets. xmit_skb() clears the whole header, so the device
sees a zero tstamp.
On receive in big packet mode, the header is placed in front of the
data in the first page. Grow padded_vnet_hdr to 48 bytes so the
40 byte header still fits there.
Tested in a guest with a patched QEMU:
guest: ./timestamping eth0 \
SOF_TIMESTAMPING_RAW_HARDWARE \
SOF_TIMESTAMPING_RX_HARDWARE
host: nc -4 -u 192.168.1.1 319
Based on work by Willem de Bruijn.
Signed-off-by: Steffen Trumtrar <s.trumtrar@pengutronix.de>
---
drivers/net/virtio_net.c | 131 +++++++++++++++++++++++++++++++++++-----
include/linux/virtio_net.h | 10 +++
include/uapi/linux/virtio_net.h | 1 +
3 files changed, 127 insertions(+), 15 deletions(-)
diff --git a/drivers/net/virtio_net.c b/drivers/net/virtio_net.c
index 23e7f62a6ff64..57b7c32625dfb 100644
--- a/drivers/net/virtio_net.c
+++ b/drivers/net/virtio_net.c
@@ -20,6 +20,7 @@
#include <linux/filter.h>
#include <linux/kernel.h>
#include <linux/dim.h>
+#include <linux/unaligned.h>
#include <net/route.h>
#include <net/xdp.h>
#include <net/net_failover.h>
@@ -418,6 +419,12 @@ struct virtnet_info {
u32 rss_hash_types_supported;
u32 rss_hash_types_saved;
+ /* Device passes time stamps to the driver */
+ bool rx_tstamp_supported;
+ bool rx_tstamp_enabled;
+
+ struct kernel_hwtstamp_config tstamp_config;
+
/* Has control virtqueue */
bool has_cvq;
@@ -482,15 +489,17 @@ static_assert(offsetof(struct virtnet_info, rss_trailer.hash_key_data) ==
offsetof(struct virtnet_info, rss_hash_key_data));
struct padded_vnet_hdr {
- struct virtio_net_hdr_v1_hash hdr;
+ struct virtio_net_common_hdr hdr;
/*
* hdr is in a separate sg buffer, and data sg buffer shares same page
* with this header sg. This padding makes next sg 16 byte aligned
* after the header.
*/
- char padding[12];
+ char padding[8];
};
+static_assert(sizeof(struct padded_vnet_hdr) % 16 == 0);
+
/*
* The vnet header sits in the headroom in front of xdp->data, which the
* XDP program may overwrite with bpf_xdp_adjust_head() or
@@ -520,7 +529,7 @@ static int virtnet_xdp_handler(struct bpf_prog *xdp_prog, struct xdp_buff *xdp,
unsigned int *xdp_xmit,
struct virtnet_rq_stats *stats);
static void virtnet_receive_done(struct virtnet_info *vi, struct receive_queue *rq,
- struct sk_buff *skb, u8 flags);
+ struct sk_buff *skb, u8 flags, ktime_t tstamp);
static struct sk_buff *virtnet_skb_append_frag(struct receive_queue *rq,
struct sk_buff *head_skb,
struct sk_buff *curr_skb,
@@ -1299,14 +1308,25 @@ static struct sk_buff *virtnet_receive_xsk_merge(struct net_device *dev, struct
return NULL;
}
+static inline ktime_t virtnet_record_rx_tstamp(const struct virtnet_info *vi,
+ const struct virtio_net_common_hdr *hdr)
+{
+ if (!READ_ONCE(vi->rx_tstamp_enabled))
+ return 0;
+
+ return ns_to_ktime(get_unaligned_le64(hdr->tstamp));
+}
+
static void virtnet_receive_xsk_buf(struct virtnet_info *vi, struct receive_queue *rq,
void *buf, u32 len,
unsigned int *xdp_xmit,
struct virtnet_rq_stats *stats)
{
struct net_device *dev = vi->dev;
+ struct virtio_net_common_hdr *hdr;
struct sk_buff *skb = NULL;
struct xdp_buff *xdp;
+ ktime_t tstamp;
u8 flags;
len -= vi->hdr_len;
@@ -1324,7 +1344,9 @@ static void virtnet_receive_xsk_buf(struct virtnet_info *vi, struct receive_queu
return;
}
- flags = ((struct virtio_net_common_hdr *)(xdp->data - vi->hdr_len))->hdr.flags;
+ hdr = (struct virtio_net_common_hdr *)(xdp->data - vi->hdr_len);
+ flags = hdr->hdr.flags;
+ tstamp = virtnet_record_rx_tstamp(vi, hdr);
if (!vi->mergeable_rx_bufs)
skb = virtnet_receive_xsk_small(dev, vi, rq, xdp, xdp_xmit, stats);
@@ -1332,7 +1354,7 @@ static void virtnet_receive_xsk_buf(struct virtnet_info *vi, struct receive_queu
skb = virtnet_receive_xsk_merge(dev, vi, rq, xdp, xdp_xmit, stats);
if (skb)
- virtnet_receive_done(vi, rq, skb, flags);
+ virtnet_receive_done(vi, rq, skb, flags, tstamp);
}
static int virtnet_add_recvbuf_xsk(struct virtnet_info *vi, struct receive_queue *rq,
@@ -2540,7 +2562,7 @@ static void virtio_skb_set_hash(const struct virtio_net_hdr_v1_hash *hdr_hash,
}
static void virtnet_receive_done(struct virtnet_info *vi, struct receive_queue *rq,
- struct sk_buff *skb, u8 flags)
+ struct sk_buff *skb, u8 flags, ktime_t tstamp)
{
struct virtio_net_common_hdr *hdr;
struct net_device *dev = vi->dev;
@@ -2549,6 +2571,9 @@ static void virtnet_receive_done(struct virtnet_info *vi, struct receive_queue *
if (dev->features & NETIF_F_RXHASH && vi->has_rss_hash_report)
virtio_skb_set_hash(&hdr->hash_v1_hdr, skb);
+ if (tstamp)
+ skb_hwtstamps(skb)->hwtstamp = tstamp;
+
hdr->hdr.flags = flags;
if (virtio_net_handle_csum_offload(skb, &hdr->hdr, vi->rx_tnl_csum)) {
net_warn_ratelimited("%s: bad csum: flags: %x, gso_type: %x rx_tnl_csum %d\n",
@@ -2587,8 +2612,10 @@ static void receive_buf(struct virtnet_info *vi, struct receive_queue *rq,
unsigned int *xdp_xmit,
struct virtnet_rq_stats *stats)
{
+ struct virtio_net_common_hdr *hdr;
struct net_device *dev = vi->dev;
struct sk_buff *skb;
+ ktime_t tstamp;
u8 flags;
if (unlikely(len < vi->hdr_len + ETH_HLEN)) {
@@ -2618,23 +2645,27 @@ static void receive_buf(struct virtnet_info *vi, struct receive_queue *rq,
*/
if (vi->mergeable_rx_bufs) {
- flags = ((struct virtio_net_common_hdr *)buf)->hdr.flags;
+ hdr = buf;
+ flags = hdr->hdr.flags;
+ tstamp = virtnet_record_rx_tstamp(vi, hdr);
skb = receive_mergeable(dev, vi, rq, buf, ctx, len, xdp_xmit,
stats);
} else if (vi->big_packets) {
- void *p = page_address((struct page *)buf);
-
- flags = ((struct virtio_net_common_hdr *)p)->hdr.flags;
+ hdr = page_address((struct page *)buf);
+ flags = hdr->hdr.flags;
+ tstamp = virtnet_record_rx_tstamp(vi, hdr);
skb = receive_big(dev, vi, rq, buf, len, stats);
} else {
- flags = ((struct virtio_net_common_hdr *)buf)->hdr.flags;
+ hdr = buf;
+ flags = hdr->hdr.flags;
+ tstamp = virtnet_record_rx_tstamp(vi, hdr);
skb = receive_small(dev, vi, rq, buf, ctx, len, xdp_xmit, stats);
}
if (unlikely(!skb))
return;
- virtnet_receive_done(vi, rq, skb, flags);
+ virtnet_receive_done(vi, rq, skb, flags, tstamp);
}
static int virtnet_rq_submit(struct receive_queue *rq, char *buf,
@@ -5575,6 +5606,27 @@ static int virtnet_get_per_queue_coalesce(struct net_device *dev,
return 0;
}
+static int virtnet_get_ts_info(struct net_device *dev,
+ struct kernel_ethtool_ts_info *info)
+{
+ struct virtnet_info *vi = netdev_priv(dev);
+
+ /* setup default software timestamp */
+ ethtool_op_get_ts_info(dev, info);
+
+ if (vi->rx_tstamp_supported) {
+ info->so_timestamping |=
+ SOF_TIMESTAMPING_RX_HARDWARE |
+ SOF_TIMESTAMPING_RAW_HARDWARE;
+
+ info->tx_types = BIT(HWTSTAMP_TX_OFF);
+ info->rx_filters = BIT(HWTSTAMP_FILTER_NONE) |
+ BIT(HWTSTAMP_FILTER_ALL);
+ }
+
+ return 0;
+}
+
static void virtnet_init_settings(struct net_device *dev)
{
struct virtnet_info *vi = netdev_priv(dev);
@@ -5670,7 +5722,7 @@ static const struct ethtool_ops virtnet_ethtool_ops = {
.get_ethtool_stats = virtnet_get_ethtool_stats,
.set_channels = virtnet_set_channels,
.get_channels = virtnet_get_channels,
- .get_ts_info = ethtool_op_get_ts_info,
+ .get_ts_info = virtnet_get_ts_info,
.get_link_ksettings = virtnet_get_link_ksettings,
.set_link_ksettings = virtnet_set_link_ksettings,
.set_coalesce = virtnet_set_coalesce,
@@ -6269,6 +6321,43 @@ static void virtnet_tx_timeout(struct net_device *dev, unsigned int txqueue)
jiffies_to_usecs(jiffies - READ_ONCE(txq->trans_start)));
}
+static int virtnet_hwtstamp_get(struct net_device *dev,
+ struct kernel_hwtstamp_config *config)
+{
+ struct virtnet_info *vi = netdev_priv(dev);
+
+ if (!vi->rx_tstamp_supported)
+ return -EOPNOTSUPP;
+
+ *config = vi->tstamp_config;
+
+ return 0;
+}
+
+static int virtnet_hwtstamp_set(struct net_device *dev,
+ struct kernel_hwtstamp_config *config,
+ struct netlink_ext_ack *extack)
+{
+ struct virtnet_info *vi = netdev_priv(dev);
+
+ if (!vi->rx_tstamp_supported)
+ return -EOPNOTSUPP;
+
+ if (config->tx_type != HWTSTAMP_TX_OFF) {
+ NL_SET_ERR_MSG_MOD(extack, "TX timestamping is not supported");
+ return -ERANGE;
+ }
+
+ /* The driver cannot filter, any filter becomes ALL */
+ if (config->rx_filter != HWTSTAMP_FILTER_NONE)
+ config->rx_filter = HWTSTAMP_FILTER_ALL;
+
+ WRITE_ONCE(vi->rx_tstamp_enabled, config->rx_filter == HWTSTAMP_FILTER_ALL);
+ vi->tstamp_config = *config;
+
+ return 0;
+}
+
static int virtnet_init_irq_moder(struct virtnet_info *vi)
{
u8 profile_flags = 0, coal_flags = 0;
@@ -6329,6 +6418,8 @@ static const struct net_device_ops virtnet_netdev = {
.ndo_get_phys_port_name = virtnet_get_phys_port_name,
.ndo_set_features = virtnet_set_features,
.ndo_tx_timeout = virtnet_tx_timeout,
+ .ndo_hwtstamp_set = virtnet_hwtstamp_set,
+ .ndo_hwtstamp_get = virtnet_hwtstamp_get,
};
static void virtnet_config_changed_work(struct work_struct *work)
@@ -6740,6 +6831,10 @@ static int virtnet_validate(struct virtio_device *vdev)
__virtio_clear_bit(vdev, VIRTIO_NET_F_STANDBY);
}
+ /* The fixed header layout with tstamp at offset 32 needs VERSION_1 */
+ if (!virtio_has_feature(vdev, VIRTIO_F_VERSION_1))
+ __virtio_clear_bit(vdev, VIRTIO_NET_F_RX_TSTAMP);
+
return 0;
}
@@ -6932,6 +7027,9 @@ static int virtnet_probe(struct virtio_device *vdev)
if (virtio_has_feature(vdev, VIRTIO_NET_F_HASH_REPORT))
vi->has_rss_hash_report = true;
+ if (virtio_has_feature(vdev, VIRTIO_NET_F_RX_TSTAMP))
+ vi->rx_tstamp_supported = true;
+
if (virtio_has_feature(vdev, VIRTIO_NET_F_RSS)) {
vi->has_rss = true;
@@ -6965,8 +7063,10 @@ static int virtnet_probe(struct virtio_device *vdev)
dev->xdp_metadata_ops = &virtnet_xdp_metadata_ops;
}
- if (virtio_has_feature(vdev, VIRTIO_NET_F_GUEST_UDP_TUNNEL_GSO) ||
- virtio_has_feature(vdev, VIRTIO_NET_F_HOST_UDP_TUNNEL_GSO))
+ if (vi->rx_tstamp_supported)
+ vi->hdr_len = offsetofend(struct virtio_net_common_hdr, tstamp);
+ else if (virtio_has_feature(vdev, VIRTIO_NET_F_GUEST_UDP_TUNNEL_GSO) ||
+ virtio_has_feature(vdev, VIRTIO_NET_F_HOST_UDP_TUNNEL_GSO))
vi->hdr_len = sizeof(struct virtio_net_hdr_v1_hash_tunnel);
else if (vi->has_rss_hash_report)
vi->hdr_len = sizeof(struct virtio_net_hdr_v1_hash);
@@ -7309,6 +7409,7 @@ static unsigned int features[] = {
VIRTIO_NET_F_GUEST_UDP_TUNNEL_GSO_CSUM,
VIRTIO_NET_F_HOST_UDP_TUNNEL_GSO,
VIRTIO_NET_F_HOST_UDP_TUNNEL_GSO_CSUM,
+ VIRTIO_NET_F_RX_TSTAMP,
};
static unsigned int features_legacy[] = {
diff --git a/include/linux/virtio_net.h b/include/linux/virtio_net.h
index a6de308b8f25e..2abd8a614a23f 100644
--- a/include/linux/virtio_net.h
+++ b/include/linux/virtio_net.h
@@ -17,6 +17,16 @@ struct virtio_net_common_hdr {
struct virtio_net_hdr_v1_hash hash_v1_hdr;
struct virtio_net_hdr_v1_hash_tunnel tnl_hdr;
};
+ /*
+ * The wire layout is fixed and cumulative: a field sits at the same
+ * offset whether or not the features before it were negotiated; unused
+ * bytes are padding. tstamp is thus always at offset 32 and only
+ * present (header size 40) if VIRTIO_NET_F_RX_TSTAMP was negotiated.
+ * The negotiated header size tells how much of this struct is
+ * on the wire. Only ever append new fields at the end.
+ */
+ __u8 reserved[8]; /* outer_nh_offset, ipsec_resource_hdr (not implemented) */
+ __le16 tstamp[4]; /* le64 TAI receive time in ns, 0 if none */
};
static inline bool virtio_net_hdr_match_proto(__be16 protocol, __u8 gso_type)
diff --git a/include/uapi/linux/virtio_net.h b/include/uapi/linux/virtio_net.h
index 1db45b01532b5..15e8e408d50fb 100644
--- a/include/uapi/linux/virtio_net.h
+++ b/include/uapi/linux/virtio_net.h
@@ -56,6 +56,7 @@
#define VIRTIO_NET_F_MQ 22 /* Device supports Receive Flow
* Steering */
#define VIRTIO_NET_F_CTRL_MAC_ADDR 23 /* Set MAC address */
+#define VIRTIO_NET_F_RX_TSTAMP 49 /* Device sends TAI receive time */
#define VIRTIO_NET_F_DEVICE_STATS 50 /* Device can provide device-level statistics. */
#define VIRTIO_NET_F_VQ_NOTF_COAL 52 /* Device supports virtqueue notification coalescing */
#define VIRTIO_NET_F_NOTF_COAL 53 /* Device supports notifications coalescing */
--
2.56.0
^ permalink raw reply [flat|nested] 11+ messages in thread* [PATCH net-next v5 6/9] virtio-net: assert header sizes
2026-11-12 11:19 [PATCH net-next v5 0/9] virtio-net: add receive timestamp support Steffen Trumtrar
` (4 preceding siblings ...)
2026-11-12 11:19 ` [PATCH net-next v5 5/9] virtio-net: support receive timestamp Steffen Trumtrar
@ 2026-11-12 11:19 ` Steffen Trumtrar
2026-11-12 11:19 ` [PATCH net-next v5 7/9] vhost-net: support VIRTIO_NET_F_RX_TSTAMP Steffen Trumtrar
` (3 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Steffen Trumtrar @ 2026-11-12 11:19 UTC (permalink / raw)
To: Michael S. Tsirkin, Xuan Zhuo, David S. Miller, Jakub Kicinski,
Paolo Abeni, Richard Cochran, Willem de Bruijn, Andrew Lunn,
Eugenio Pérez, Jason Wang, Alexei Starovoitov,
Daniel Borkmann, Jesper Dangaard Brouer, John Fastabend,
Stanislav Fomichev, Eric Dumazet, Andrew Lunn, Simon Horman,
Kory Maincent, Liang Chen, Heng Qi
Cc: virtualization, netdev, linux-kernel, bpf, kvm, kernel, Steffen Trumtrar
The header is cast from skb->cb and from device buffers, so its layout
must not change silently. Add static_asserts for the offset of tstamp,
the total size, the alignment, and that the header fits into skb->cb.
Signed-off-by: Steffen Trumtrar <s.trumtrar@pengutronix.de>
---
include/linux/virtio_net.h | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/include/linux/virtio_net.h b/include/linux/virtio_net.h
index 2abd8a614a23f..e7776a37a486b 100644
--- a/include/linux/virtio_net.h
+++ b/include/linux/virtio_net.h
@@ -29,6 +29,17 @@ struct virtio_net_common_hdr {
__le16 tstamp[4]; /* le64 TAI receive time in ns, 0 if none */
};
+/*
+ * The header is cast from skb->cb and from device buffers, the layout must not
+ * change silently.
+ */
+static_assert(offsetof(struct virtio_net_common_hdr, tstamp) == 32);
+static_assert(sizeof(struct virtio_net_common_hdr) ==
+ offsetofend(struct virtio_net_common_hdr, tstamp));
+static_assert(__alignof__(struct virtio_net_common_hdr) == 2);
+/* virtio_net_common_hdr must fit into skb->cb */
+static_assert(sizeof(struct virtio_net_common_hdr) <= sizeof_field(struct sk_buff, cb));
+
static inline bool virtio_net_hdr_match_proto(__be16 protocol, __u8 gso_type)
{
switch (gso_type & ~VIRTIO_NET_HDR_GSO_ECN) {
--
2.56.0
^ permalink raw reply [flat|nested] 11+ messages in thread* [PATCH net-next v5 7/9] vhost-net: support VIRTIO_NET_F_RX_TSTAMP
2026-11-12 11:19 [PATCH net-next v5 0/9] virtio-net: add receive timestamp support Steffen Trumtrar
` (5 preceding siblings ...)
2026-11-12 11:19 ` [PATCH net-next v5 6/9] virtio-net: assert header sizes Steffen Trumtrar
@ 2026-11-12 11:19 ` Steffen Trumtrar
2026-11-12 11:19 ` [PATCH net-next v5 8/9] virtio-net: add XDP rx timestamp metadata Steffen Trumtrar
` (2 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Steffen Trumtrar @ 2026-11-12 11:19 UTC (permalink / raw)
To: Michael S. Tsirkin, Xuan Zhuo, David S. Miller, Jakub Kicinski,
Paolo Abeni, Richard Cochran, Willem de Bruijn, Andrew Lunn,
Eugenio Pérez, Jason Wang, Alexei Starovoitov,
Daniel Borkmann, Jesper Dangaard Brouer, John Fastabend,
Stanislav Fomichev, Eric Dumazet, Andrew Lunn, Simon Horman,
Kory Maincent, Liang Chen, Heng Qi
Cc: virtualization, netdev, linux-kernel, bpf, kvm, kernel, Steffen Trumtrar
Advertise VIRTIO_NET_F_RX_TSTAMP to userspace and size the vnet header
for it, so a guest can negotiate the feature with a vhost-net backed
tap device.
The header is 40 bytes if the feature is negotiated. Whoever writes
the header (tun, or vhost itself with VHOST_NET_F_VIRTIO_NET_HDR) has
to fill in the timestamp; vhost only reserves the room. If vhost
writes the header, it zeroes everything behind the fields it fills in
(num_buffers only with VIRTIO_F_VERSION_1 or VIRTIO_NET_F_MRG_RXBUF),
so the timestamp is 0, which the guest ignores.
Signed-off-by: Steffen Trumtrar <s.trumtrar@pengutronix.de>
---
drivers/vhost/net.c | 21 ++++++++++++++++++++-
1 file changed, 20 insertions(+), 1 deletion(-)
diff --git a/drivers/vhost/net.c b/drivers/vhost/net.c
index 435d8ed3851f3..1b171af105a22 100644
--- a/drivers/vhost/net.c
+++ b/drivers/vhost/net.c
@@ -77,7 +77,8 @@ static const int vhost_net_bits[] = {
VIRTIO_F_RING_RESET,
VIRTIO_F_IN_ORDER,
VIRTIO_NET_F_GUEST_UDP_TUNNEL_GSO,
- VIRTIO_NET_F_HOST_UDP_TUNNEL_GSO
+ VIRTIO_NET_F_HOST_UDP_TUNNEL_GSO,
+ VIRTIO_NET_F_RX_TSTAMP
};
enum {
@@ -1166,6 +1167,7 @@ static void handle_rx(struct vhost_net *net)
s16 headcount;
size_t vhost_hlen, sock_hlen;
size_t vhost_len, sock_len;
+ size_t hdr_filled;
bool busyloop_intr = false;
bool set_num_buffers;
struct socket *sock;
@@ -1193,6 +1195,9 @@ static void handle_rx(struct vhost_net *net)
mergeable = vhost_has_feature(vq, VIRTIO_NET_F_MRG_RXBUF);
set_num_buffers = mergeable ||
vhost_has_feature(vq, VIRTIO_F_VERSION_1);
+ /* Header bytes written below, if vhost supplies the header */
+ hdr_filled = sizeof(hdr) +
+ (set_num_buffers ? sizeof(num_buffers) : 0);
do {
sock_len = vhost_net_rx_peek_head_len(net, sock->sk,
@@ -1278,6 +1283,16 @@ static void handle_rx(struct vhost_net *net)
vhost_discard_vq_desc(vq, headcount, ndesc);
goto out;
}
+ /* Zero the rest of a header supplied by vhost, e.g. tstamp */
+ if (unlikely(vhost_hlen > hdr_filled)) {
+ size_t rest = vhost_hlen - hdr_filled;
+
+ if (iov_iter_zero(rest, &fixup) != rest) {
+ vq_err(vq, "Failed vnet_hdr zeroing");
+ vhost_discard_vq_desc(vq, headcount, ndesc);
+ goto out;
+ }
+ }
nvq->done_idx += headcount;
count += in_order ? 1 : headcount;
if (nvq->done_idx > VHOST_NET_BATCH) {
@@ -1684,6 +1699,10 @@ static int vhost_net_set_features(struct vhost_net *n, const u64 *features)
VIRTIO_NET_F_GUEST_UDP_TUNNEL_GSO))
hdr_len = sizeof(struct virtio_net_hdr_v1_hash_tunnel);
+ /* The header layout is cumulative, tstamp sits behind all other fields. */
+ if (virtio_features_test_bit(features, VIRTIO_NET_F_RX_TSTAMP))
+ hdr_len = offsetofend(struct virtio_net_common_hdr, tstamp);
+
if (virtio_features_test_bit(features, VHOST_NET_F_VIRTIO_NET_HDR)) {
/* vhost provides vnet_hdr */
vhost_hlen = hdr_len;
--
2.56.0
^ permalink raw reply [flat|nested] 11+ messages in thread* [PATCH net-next v5 8/9] virtio-net: add XDP rx timestamp metadata
2026-11-12 11:19 [PATCH net-next v5 0/9] virtio-net: add receive timestamp support Steffen Trumtrar
` (6 preceding siblings ...)
2026-11-12 11:19 ` [PATCH net-next v5 7/9] vhost-net: support VIRTIO_NET_F_RX_TSTAMP Steffen Trumtrar
@ 2026-11-12 11:19 ` Steffen Trumtrar
2026-11-12 11:19 ` [PATCH net-next v5 9/9] tun: add rx timestamp to the vnet header Steffen Trumtrar
2026-11-12 11:26 ` [PATCH net-next v5 0/9] virtio-net: add receive timestamp support netdev-bot+sinfo
9 siblings, 0 replies; 11+ messages in thread
From: Steffen Trumtrar @ 2026-11-12 11:19 UTC (permalink / raw)
To: Michael S. Tsirkin, Xuan Zhuo, David S. Miller, Jakub Kicinski,
Paolo Abeni, Richard Cochran, Willem de Bruijn, Andrew Lunn,
Eugenio Pérez, Jason Wang, Alexei Starovoitov,
Daniel Borkmann, Jesper Dangaard Brouer, John Fastabend,
Stanislav Fomichev, Eric Dumazet, Andrew Lunn, Simon Horman,
Kory Maincent, Liang Chen, Heng Qi
Cc: virtualization, netdev, linux-kernel, bpf, kvm, kernel, Steffen Trumtrar
For the skb, the timestamp is read from the header before the XDP
program runs, so XDP_PASS keeps it even if the program changes the
packet. XDP therefore works with rx timestamping enabled.
Add xmo_rx_timestamp so XDP programs can read the timestamp with
bpf_xdp_metadata_rx_timestamp(). It returns -ENODATA if timestamping
is not enabled or the device did not timestamp the packet. Install the
metadata ops also if only VIRTIO_NET_F_RX_TSTAMP is negotiated;
rx_hash returns -ENODATA then.
Like the hash, the timestamp is copied into struct virtnet_xdp_buff
before the program runs, so the callback still works after the program
changed the headroom with bpf_xdp_adjust_head() or
bpf_xdp_adjust_meta().
Signed-off-by: Steffen Trumtrar <s.trumtrar@pengutronix.de>
---
drivers/net/virtio_net.c | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/drivers/net/virtio_net.c b/drivers/net/virtio_net.c
index 57b7c32625dfb..46be686e34b79 100644
--- a/drivers/net/virtio_net.c
+++ b/drivers/net/virtio_net.c
@@ -511,6 +511,7 @@ struct virtnet_xdp_buff {
struct xdp_buff xdp;
u32 hash_value;
u16 hash_report;
+ u64 tstamp;
};
static inline u32
@@ -1736,6 +1737,8 @@ static void virtnet_xdp_save_hdr(const struct virtnet_info *vi,
ctx->hash_value = 0;
ctx->hash_report = VIRTIO_NET_HASH_REPORT_NONE;
}
+
+ ctx->tstamp = virtnet_record_rx_tstamp(vi, xdp->data - vi->hdr_len);
}
static int virtnet_xdp_handler(struct bpf_prog *xdp_prog, struct xdp_buff *xdp,
@@ -6896,8 +6899,21 @@ static int virtnet_xdp_rx_hash(const struct xdp_md *_ctx, u32 *hash,
return 0;
}
+static int virtnet_xdp_rx_timestamp(const struct xdp_md *_ctx, u64 *timestamp)
+{
+ const struct virtnet_xdp_buff *ctx = (void *)_ctx;
+
+ /* 0: timestamping is off or the device did not timestamp the packet */
+ if (!ctx->tstamp)
+ return -ENODATA;
+
+ *timestamp = ctx->tstamp;
+ return 0;
+}
+
static const struct xdp_metadata_ops virtnet_xdp_metadata_ops = {
.xmo_rx_hash = virtnet_xdp_rx_hash,
+ .xmo_rx_timestamp = virtnet_xdp_rx_timestamp,
};
static int virtnet_probe(struct virtio_device *vdev)
@@ -7063,6 +7079,10 @@ static int virtnet_probe(struct virtio_device *vdev)
dev->xdp_metadata_ops = &virtnet_xdp_metadata_ops;
}
+ /* rx_hash returns -ENODATA without NETIF_F_RXHASH */
+ if (vi->rx_tstamp_supported)
+ dev->xdp_metadata_ops = &virtnet_xdp_metadata_ops;
+
if (vi->rx_tstamp_supported)
vi->hdr_len = offsetofend(struct virtio_net_common_hdr, tstamp);
else if (virtio_has_feature(vdev, VIRTIO_NET_F_GUEST_UDP_TUNNEL_GSO) ||
--
2.56.0
^ permalink raw reply [flat|nested] 11+ messages in thread* [PATCH net-next v5 9/9] tun: add rx timestamp to the vnet header
2026-11-12 11:19 [PATCH net-next v5 0/9] virtio-net: add receive timestamp support Steffen Trumtrar
` (7 preceding siblings ...)
2026-11-12 11:19 ` [PATCH net-next v5 8/9] virtio-net: add XDP rx timestamp metadata Steffen Trumtrar
@ 2026-11-12 11:19 ` Steffen Trumtrar
2026-11-12 11:26 ` [PATCH net-next v5 0/9] virtio-net: add receive timestamp support netdev-bot+sinfo
9 siblings, 0 replies; 11+ messages in thread
From: Steffen Trumtrar @ 2026-11-12 11:19 UTC (permalink / raw)
To: Michael S. Tsirkin, Xuan Zhuo, David S. Miller, Jakub Kicinski,
Paolo Abeni, Richard Cochran, Willem de Bruijn, Andrew Lunn,
Eugenio Pérez, Jason Wang, Alexei Starovoitov,
Daniel Borkmann, Jesper Dangaard Brouer, John Fastabend,
Stanislav Fomichev, Eric Dumazet, Andrew Lunn, Simon Horman,
Kory Maincent, Liang Chen, Heng Qi
Cc: virtualization, netdev, linux-kernel, bpf, kvm, kernel, Steffen Trumtrar
Reference implementation of the device side of VIRTIO_NET_F_RX_TSTAMP: if
the configured vnet_hdr_sz covers the tstamp field, write the current
TAI time into the header when a packet is handed to userspace or vhost.
__tun_vnet_hdr_put() now takes the full struct virtio_net_common_hdr
and copies as much of it as vnet_hdr_sz holds.
This is not intended to be merged. A full test needs a QEMU that knows
the feature and sets the matching vnet_hdr_sz in
virtio_net_set_mrg_rx_bufs(); see
https://github.com/strumtrar/qemu/tree/v11.0.1/virtio-rx-stamps
Signed-off-by: Steffen Trumtrar <s.trumtrar@pengutronix.de>
---
drivers/net/tun.c | 14 +++++++++-----
drivers/net/tun_vnet.h | 18 ++++++++++++------
2 files changed, 21 insertions(+), 11 deletions(-)
diff --git a/drivers/net/tun.c b/drivers/net/tun.c
index 8920c797acfd7..1a8c3377753d7 100644
--- a/drivers/net/tun.c
+++ b/drivers/net/tun.c
@@ -57,6 +57,7 @@
#include <linux/math.h>
#include <linux/nsproxy.h>
#include <linux/virtio_net.h>
+#include <linux/unaligned.h>
#include <linux/rcupdate.h>
#include <net/net_namespace.h>
#include <net/netns/generic.h>
@@ -2168,12 +2169,16 @@ static ssize_t tun_put_user(struct tun_struct *tun,
}
if (vnet_hdr_sz) {
- struct virtio_net_hdr_v1_hash_tunnel hdr;
- struct virtio_net_hdr *gso;
+ struct virtio_net_common_hdr hdr;
memset(&hdr, 0, sizeof(hdr));
+
+ /* The configured header covers the tstamp field */
+ if (vnet_hdr_sz >= offsetofend(struct virtio_net_common_hdr, tstamp))
+ put_unaligned_le64(ktime_get_clocktai_ns(), hdr.tstamp);
+
ret = tun_vnet_hdr_tnl_from_skb(tun->flags, tun->dev, skb,
- &hdr);
+ &hdr.tnl_hdr);
if (ret)
return ret;
@@ -2181,9 +2186,8 @@ static ssize_t tun_put_user(struct tun_struct *tun,
* Drop the packet if the configured header size is too small
* WRT the enabled offloads.
*/
- gso = (struct virtio_net_hdr *)&hdr;
ret = __tun_vnet_hdr_put(vnet_hdr_sz, tun->dev->features,
- iter, gso);
+ iter, &hdr);
if (ret)
return ret;
}
diff --git a/drivers/net/tun_vnet.h b/drivers/net/tun_vnet.h
index f4c652b1fa44d..d4cc56dac98d6 100644
--- a/drivers/net/tun_vnet.h
+++ b/drivers/net/tun_vnet.h
@@ -153,19 +153,21 @@ static inline int tun_vnet_hdr_get(int sz, unsigned int flags,
return __tun_vnet_hdr_get(sz, flags, 0, from, hdr);
}
+/* hdr is fully initialized, copy as much of it as the configured size holds */
static inline int __tun_vnet_hdr_put(int sz, netdev_features_t features,
struct iov_iter *iter,
- const struct virtio_net_hdr *hdr)
+ const struct virtio_net_common_hdr *hdr)
{
- unsigned int parsed_size = tun_vnet_parse_size(features);
+ unsigned int put_size = min_t(unsigned int, sizeof(*hdr), sz);
- if (unlikely(iov_iter_count(iter) < sz))
+ if (unlikely(iov_iter_count(iter) < sz ||
+ sz < tun_vnet_parse_size(features)))
return -EINVAL;
- if (unlikely(copy_to_iter(hdr, parsed_size, iter) != parsed_size))
+ if (unlikely(copy_to_iter(hdr, put_size, iter) != put_size))
return -EFAULT;
- if (iov_iter_zero(sz - parsed_size, iter) != sz - parsed_size)
+ if (iov_iter_zero(sz - put_size, iter) != sz - put_size)
return -EFAULT;
return 0;
@@ -174,7 +176,11 @@ static inline int __tun_vnet_hdr_put(int sz, netdev_features_t features,
static inline int tun_vnet_hdr_put(int sz, struct iov_iter *iter,
const struct virtio_net_hdr *hdr)
{
- return __tun_vnet_hdr_put(sz, 0, iter, hdr);
+ struct virtio_net_common_hdr common_hdr = {};
+
+ common_hdr.hdr = *hdr;
+
+ return __tun_vnet_hdr_put(sz, 0, iter, &common_hdr);
}
static inline int tun_vnet_hdr_to_skb(unsigned int flags, struct sk_buff *skb,
--
2.56.0
^ permalink raw reply [flat|nested] 11+ messages in thread* Re: [PATCH net-next v5 0/9] virtio-net: add receive timestamp support
2026-11-12 11:19 [PATCH net-next v5 0/9] virtio-net: add receive timestamp support Steffen Trumtrar
` (8 preceding siblings ...)
2026-11-12 11:19 ` [PATCH net-next v5 9/9] tun: add rx timestamp to the vnet header Steffen Trumtrar
@ 2026-11-12 11:26 ` netdev-bot+sinfo
9 siblings, 0 replies; 11+ messages in thread
From: netdev-bot+sinfo @ 2026-11-12 11:26 UTC (permalink / raw)
To: Steffen Trumtrar
Cc: Michael S. Tsirkin, Xuan Zhuo, David S. Miller, Jakub Kicinski,
Paolo Abeni, Richard Cochran, Willem de Bruijn, Andrew Lunn,
Eugenio Pérez, Jason Wang, Alexei Starovoitov,
Daniel Borkmann, Jesper Dangaard Brouer, John Fastabend,
Stanislav Fomichev, Eric Dumazet, Andrew Lunn, Simon Horman,
Kory Maincent, Liang Chen, Heng Qi, virtualization, netdev,
linux-kernel, bpf, kvm, kernel
Hi!
This is an automated message. This series looks like a fix, but its
commit messages seem to be missing some information:
- Whether the issue was actually triggered, or is only theoretical
(e.g. found by LLM and not proven at runtime). If it was triggered
please include the symptoms, like the stack trace or kernel messages.
Please do not repost the series just to address the above. Instead,
reply to this email with the missing information, so that reviewers
can take it into account. If the series needs another revision for
other reasons, please include the information in the commit messages
then.
The evaluation is done by an LLM so it may be wrong, if you think
that is the case please reply and explain.
^ permalink raw reply [flat|nested] 11+ messages in thread