* [PATCH net-next v4 1/6] net: rtnetlink: add pacing_offload_horizon attribute to net_device
2026-08-06 23:25 [PATCH net-next v4 0/6] hardware pacing offload Willem de Bruijn
@ 2026-08-06 23:25 ` Willem de Bruijn
2026-08-06 23:25 ` [PATCH net-next v4 2/6] net_sched: sch_fq: clear past skb->tstamp if offloading pacing Willem de Bruijn
` (5 subsequent siblings)
6 siblings, 0 replies; 10+ messages in thread
From: Willem de Bruijn @ 2026-08-06 23:25 UTC (permalink / raw)
To: netdev
Cc: intel-wired-lan, anthony.l.nguyen, joshua.a.hay,
przemyslaw.kitszel, Willem de Bruijn
From: Willem de Bruijn <willemb@google.com>
The 'max_pacing_offload_horizon' field of 'struct net_device' represents
the maximum pacing offload horizon supported by the device.
Add a new field 'pacing_offload_horizon' to store the active pacing
offload horizon.
The new attribute is initialized to 0 (disabled) and can be set from
userspace via RTM_SETLINK up to dev->max_pacing_offload_horizon. This
new default off behavior does not cause regressions, as no driver yet
advertises max_pacing_offload_horizon.
The equivalent RTM_NEWLINK is absent, as a value may need to be
compared to a device maximum, which may be negotiated with the device
firmware on init, as is the case for the idpf driver in this series.
Make both fields u32, to maintain net_device cacheline layout. This
expresses up to 4s of pacing offload, which is sufficient.
Update the YNL specification ('rt-link.yaml') to add the
'pacing-offload-horizon' attribute and include it in link-all-attrs.
Both fields can be read with
python3 tools/net/ynl/pyynl/cli.py \
--spec Documentation/netlink/specs/rt-link.yaml \
--do getlink \
--json '{"ifname": "eth0"}' | grep pacing
And the active horizon set with
python3 tools/net/ynl/pyynl/cli.py \
--spec Documentation/netlink/specs/rt-link.yaml \
--do setlink \
--json '{"ifname": "eth0", "pacing-offload-horizon": 50000000}'
Signed-off-by: Willem de Bruijn <willemb@google.com>
---
Documentation/netlink/specs/rt-link.yaml | 6 ++++++
.../networking/net_cachelines/net_device.rst | 3 ++-
include/linux/netdevice.h | 4 +++-
include/uapi/linux/if_link.h | 1 +
net/core/rtnetlink.c | 21 +++++++++++++++++++
5 files changed, 33 insertions(+), 2 deletions(-)
diff --git a/Documentation/netlink/specs/rt-link.yaml b/Documentation/netlink/specs/rt-link.yaml
index 68c26a70bb64..a620135baba3 100644
--- a/Documentation/netlink/specs/rt-link.yaml
+++ b/Documentation/netlink/specs/rt-link.yaml
@@ -1080,6 +1080,10 @@ attribute-sets:
-
name: tailroom
type: u16
+ -
+ name: pacing-offload-horizon
+ type: uint
+ doc: EDT offload horizon setting for the device (in nsec).
-
name: prop-list-link-attrs
subset-of: link-attrs
@@ -2548,6 +2552,8 @@ operations:
- devlink-port
- gso-ipv4-max-size
- gro-ipv4-max-size
+ - max-pacing-offload-horizon
+ - pacing-offload-horizon
dump:
request:
value: 18
diff --git a/Documentation/networking/net_cachelines/net_device.rst b/Documentation/networking/net_cachelines/net_device.rst
index 512f6d6fa3d8..90e257e815b1 100644
--- a/Documentation/networking/net_cachelines/net_device.rst
+++ b/Documentation/networking/net_cachelines/net_device.rst
@@ -183,7 +183,8 @@ struct devlink_port* devlink_port
struct dpll_pin* dpll_pin
struct hlist_head page_pools
struct dim_irq_moder* irq_moder
-u64 max_pacing_offload_horizon
+u32 max_pacing_offload_horizon
+u32 pacing_offload_horizon
struct_napi_config* napi_config
unsigned_long gro_flush_timeout
u32 napi_defer_hard_irqs
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index 62cfad7e6b79..93d953caae94 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -2131,6 +2131,7 @@ enum netdev_reg_state {
* where the clock is recovered.
*
* @max_pacing_offload_horizon: max EDT offload horizon in nsec.
+ * @pacing_offload_horizon: EDT offload horizon setting in nsec.
* @napi_config: An array of napi_config structures containing per-NAPI
* settings.
* @num_napi_configs: number of allocated NAPI config structs,
@@ -2553,7 +2554,8 @@ struct net_device {
/** @irq_moder: dim parameters used if IS_ENABLED(CONFIG_DIMLIB). */
struct dim_irq_moder *irq_moder;
- u64 max_pacing_offload_horizon;
+ u32 max_pacing_offload_horizon;
+ u32 pacing_offload_horizon;
struct napi_config *napi_config;
u32 num_napi_configs;
u32 napi_defer_hard_irqs;
diff --git a/include/uapi/linux/if_link.h b/include/uapi/linux/if_link.h
index 43cecca49f01..7c1d7754a670 100644
--- a/include/uapi/linux/if_link.h
+++ b/include/uapi/linux/if_link.h
@@ -381,6 +381,7 @@ enum {
IFLA_NETNS_IMMUTABLE,
IFLA_HEADROOM,
IFLA_TAILROOM,
+ IFLA_PACING_OFFLOAD_HORIZON,
__IFLA_MAX
};
diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c
index 31c65a545a10..b8c35f64d49f 100644
--- a/net/core/rtnetlink.c
+++ b/net/core/rtnetlink.c
@@ -1381,6 +1381,7 @@ static noinline size_t if_nlmsg_size(const struct net_device *dev,
+ rtnl_devlink_port_size(dev)
+ rtnl_dpll_pin_size()
+ nla_total_size(8) /* IFLA_MAX_PACING_OFFLOAD_HORIZON */
+ + nla_total_size(8) /* IFLA_PACING_OFFLOAD_HORIZON */
+ nla_total_size(2) /* IFLA_HEADROOM */
+ nla_total_size(2) /* IFLA_TAILROOM */
+ rtnl_dev_parent_size(dev)
@@ -2156,6 +2157,8 @@ static int rtnl_fill_ifinfo(struct sk_buff *skb,
READ_ONCE(dev->tso_max_segs)) ||
nla_put_uint(skb, IFLA_MAX_PACING_OFFLOAD_HORIZON,
READ_ONCE(dev->max_pacing_offload_horizon)) ||
+ nla_put_uint(skb, IFLA_PACING_OFFLOAD_HORIZON,
+ READ_ONCE(dev->pacing_offload_horizon)) ||
#ifdef CONFIG_RPS
nla_put_u32(skb, IFLA_NUM_RX_QUEUES,
READ_ONCE(dev->num_rx_queues)) ||
@@ -2324,9 +2327,11 @@ static const struct nla_policy ifla_policy[IFLA_MAX+1] = {
[IFLA_ALLMULTI] = { .type = NLA_REJECT },
[IFLA_GSO_IPV4_MAX_SIZE] = NLA_POLICY_MIN(NLA_U32, MAX_TCP_HEADER + 1),
[IFLA_GRO_IPV4_MAX_SIZE] = { .type = NLA_U32 },
+ [IFLA_MAX_PACING_OFFLOAD_HORIZON] = { .type = NLA_REJECT },
[IFLA_NETNS_IMMUTABLE] = { .type = NLA_REJECT },
[IFLA_HEADROOM] = { .type = NLA_REJECT },
[IFLA_TAILROOM] = { .type = NLA_REJECT },
+ [IFLA_PACING_OFFLOAD_HORIZON] = { .type = NLA_UINT },
};
static const struct nla_policy ifla_info_policy[IFLA_INFO_MAX+1] = {
@@ -2800,6 +2805,13 @@ static int validate_linkmsg(struct net_device *dev, struct nlattr *tb[],
return -EINVAL;
}
+ if (tb[IFLA_PACING_OFFLOAD_HORIZON] &&
+ nla_get_uint(tb[IFLA_PACING_OFFLOAD_HORIZON]) >
+ dev->max_pacing_offload_horizon) {
+ NL_SET_ERR_MSG(extack, "too big pacing_offload_horizon");
+ return -EINVAL;
+ }
+
if (tb[IFLA_AF_SPEC]) {
struct nlattr *af;
int rem, err;
@@ -3317,6 +3329,15 @@ static int do_setlink(const struct sk_buff *skb, struct net_device *dev,
}
}
+ if (tb[IFLA_PACING_OFFLOAD_HORIZON]) {
+ u64 horizon = nla_get_uint(tb[IFLA_PACING_OFFLOAD_HORIZON]);
+
+ if (dev->pacing_offload_horizon ^ horizon) {
+ WRITE_ONCE(dev->pacing_offload_horizon, horizon);
+ status |= DO_SETLINK_MODIFIED;
+ }
+ }
+
if (tb[IFLA_OPERSTATE])
set_operstate(dev, nla_get_u8(tb[IFLA_OPERSTATE]));
--
2.55.0.679.g6767b8d81c-goog
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH net-next v4 2/6] net_sched: sch_fq: clear past skb->tstamp if offloading pacing
2026-08-06 23:25 [PATCH net-next v4 0/6] hardware pacing offload Willem de Bruijn
2026-08-06 23:25 ` [PATCH net-next v4 1/6] net: rtnetlink: add pacing_offload_horizon attribute to net_device Willem de Bruijn
@ 2026-08-06 23:25 ` Willem de Bruijn
2026-08-06 23:26 ` [PATCH net-next v4 3/6] idpf: support pacing offload Willem de Bruijn
` (4 subsequent siblings)
6 siblings, 0 replies; 10+ messages in thread
From: Willem de Bruijn @ 2026-08-06 23:25 UTC (permalink / raw)
To: netdev
Cc: intel-wired-lan, anthony.l.nguyen, joshua.a.hay,
przemyslaw.kitszel, Willem de Bruijn
From: Willem de Bruijn <willemb@google.com>
When hardware offload is enabled, FQ will forward packets to the
netdevice for pacing. The device has to test that skb->tstamp is
in the future.
Avoid this cost for packets whose txtime has already passed, by
clearing skb->tstamp.
Also disable timer drift logic when offload is enabled, because
time_next_packet can exceed now causing a negative value.
Signed-off-by: Willem de Bruijn <willemb@google.com>
---
Changes
v3 -> v4
- also reset tstamp_type
- minor: initialize time_next_packet for more obvious correctness
Sashiko, ignore pre-existing issues. In particular, effects on
non-EDT packets and when queue or sk rate limit is set.
Sashiko, pacing offload is an optimization. Ignore that some packets
may not get offloaded, e.g., when txtime is a few usec in the future.
Claude suggests to only call __skb_clear_delivery_time in one location
in fq_dequeue. Unfortunately the separate fastpath location is needed
as that avoids computing now in fq_dequeue for these fastpath packets.
---
include/linux/skbuff.h | 17 ++++++++++++-----
net/sched/sch_fq.c | 19 +++++++++++++++----
2 files changed, 27 insertions(+), 9 deletions(-)
diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h
index 22eda1d54a0e..b3445ad5a35c 100644
--- a/include/linux/skbuff.h
+++ b/include/linux/skbuff.h
@@ -4490,20 +4490,27 @@ static inline void skb_set_delivery_type_by_clockid(struct sk_buff *skb,
DECLARE_STATIC_KEY_FALSE(netstamp_needed_key);
-/* It is used in the ingress path to clear the delivery_time.
- * If needed, set the skb->tstamp to the (rcv) timestamp.
- */
-static __always_inline void skb_clear_delivery_time(struct sk_buff *skb)
+static __always_inline void __skb_clear_delivery_time(struct sk_buff *skb,
+ bool want_tstamp)
{
if (skb->tstamp_type) {
skb->tstamp_type = SKB_CLOCK_REALTIME;
- if (static_branch_unlikely(&netstamp_needed_key))
+ if (want_tstamp &&
+ static_branch_unlikely(&netstamp_needed_key))
skb->tstamp = ktime_get_real();
else
skb->tstamp = 0;
}
}
+/* It is used in the ingress path to clear the delivery_time.
+ * If needed, set the skb->tstamp to the (rcv) timestamp.
+ */
+static __always_inline void skb_clear_delivery_time(struct sk_buff *skb)
+{
+ __skb_clear_delivery_time(skb, true);
+}
+
static inline void skb_clear_tstamp(struct sk_buff *skb)
{
if (skb->tstamp_type)
diff --git a/net/sched/sch_fq.c b/net/sched/sch_fq.c
index 7cae082a9847..8d7458c38409 100644
--- a/net/sched/sch_fq.c
+++ b/net/sched/sch_fq.c
@@ -399,6 +399,11 @@ static struct fq_flow *fq_classify(struct Qdisc *sch, struct sk_buff *skb,
READ_ONCE(sk->sk_pacing_status) != SK_PACING_FQ)
smp_store_release(&sk->sk_pacing_status,
SK_PACING_FQ);
+
+ if (q->offload_horizon &&
+ fq_skb_cb(skb)->time_to_send <= now)
+ __skb_clear_delivery_time(skb, false);
+
return &q->internal;
}
@@ -707,6 +712,7 @@ static struct sk_buff *fq_dequeue(struct Qdisc *sch)
struct fq_sched_data *q = qdisc_priv(sch);
struct fq_perband_flows *pband;
struct fq_flow_head *head;
+ u64 time_next_packet = 0;
struct sk_buff *skb;
struct fq_flow *f;
unsigned long rate;
@@ -721,7 +727,7 @@ static struct sk_buff *fq_dequeue(struct Qdisc *sch)
if (skb) {
q->internal.qlen--;
fq_dequeue_skb(sch, &q->internal, skb);
- goto out;
+ return skb;
}
now = ktime_get_ns();
@@ -758,8 +764,8 @@ static struct sk_buff *fq_dequeue(struct Qdisc *sch)
skb = fq_peek(f);
if (skb) {
- u64 time_next_packet = max_t(u64, fq_skb_cb(skb)->time_to_send,
- f->time_next_packet);
+ time_next_packet = max_t(u64, fq_skb_cb(skb)->time_to_send,
+ f->time_next_packet);
if (now + q->offload_horizon < time_next_packet) {
head->first = f->next;
@@ -828,11 +834,16 @@ static struct sk_buff *fq_dequeue(struct Qdisc *sch)
* f->time_next_packet was set when prior packet was sent,
* and current time (@now) can be too late by tens of us.
*/
- if (f->time_next_packet)
+ if (f->time_next_packet && f->time_next_packet < now)
len -= min(len/2, now - f->time_next_packet);
f->time_next_packet = now + len;
}
+
out:
+ if (q->offload_horizon &&
+ time_next_packet && time_next_packet <= now)
+ __skb_clear_delivery_time(skb, false);
+
return skb;
}
--
2.55.0.679.g6767b8d81c-goog
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH net-next v4 3/6] idpf: support pacing offload
2026-08-06 23:25 [PATCH net-next v4 0/6] hardware pacing offload Willem de Bruijn
2026-08-06 23:25 ` [PATCH net-next v4 1/6] net: rtnetlink: add pacing_offload_horizon attribute to net_device Willem de Bruijn
2026-08-06 23:25 ` [PATCH net-next v4 2/6] net_sched: sch_fq: clear past skb->tstamp if offloading pacing Willem de Bruijn
@ 2026-08-06 23:26 ` Willem de Bruijn
2026-08-06 23:26 ` [PATCH net-next v4 4/6] selftests: drv-net: refactor so_txtime errqueue handling Willem de Bruijn
` (3 subsequent siblings)
6 siblings, 0 replies; 10+ messages in thread
From: Willem de Bruijn @ 2026-08-06 23:26 UTC (permalink / raw)
To: netdev
Cc: intel-wired-lan, anthony.l.nguyen, joshua.a.hay,
przemyslaw.kitszel, Willem de Bruijn
From: Willem de Bruijn <willemb@google.com>
If skb->tstamp is in the future, program this future delivery txtime
in the transmit descriptor.
TCP pacing offload is only offloaded if SK_PACING_FQ is negotiated and
the FQ offload_horizon is configured. But device support for pacing
offload must be more robust: it can also be reached through SO_TXTIME.
Bounds check txtime. Only packets with timestamp between now and the
horizon (pacing_offload_horizon) are offloaded.
Negotiate the feature with the device using virtchnl. Support is
conditional on
- splitq mode, where tx and tx completion queues are separate, so
completions can be returned out of order.
- flow scheduling mode, where completions can arrive out of order.
- PTP to ensure the NIC clock is synced to CLOCK_TAI.
Do not explicitly check for these preconditions. Trust the firmware to
only advertise EDT when they are met. These features are negotiated
per adapter, but expect all vports to uniformly request splitq
(req_[rt]x_splitq) and flow scheduling (flow_sch_en) when available.
Disable if in netpoll. It does not need the feature, and the ktime
functions are not safe to call in this context.
Cc: Tony Nguyen <anthony.l.nguyen@intel.com>
Cc: Przemek Kitszel <przemyslaw.kitszel@intel.com>
Cc: Joshua A Hay <joshua.a.hay@intel.com>
Signed-off-by: Willem de Bruijn <willemb@google.com>
---
Changes
v3 -> v4
- add EDT virtchnl negotiation
- move endianness fix to its own iwl-net patch
- update commit-msg: check is against pacing_offload_horizon, not max_..
- simplify assignment, avoid the le32 assignment in favor of u8 inits
- replace open coded constant with IDPF_TXD_FLOW_SCH_HORIZON_OVERFLOW_M
- (minor) fix 24b timestamp comment: is 23b + overflow bit
- (minor) remove unused parameter
v2 -> v3
- use READ_ONCE and read pacing_offload_horizon only once
v1 -> v2
- move special zero case up and return early
Sashiko, ignore pre-existing issues.
Sashiko, ignore that idpf_tx_splitq_set_txtime may have a benign race
by calling ktime_mono_to_any twice to get TAI to REALTIME offset.
---
drivers/net/ethernet/intel/idpf/idpf.h | 1 +
drivers/net/ethernet/intel/idpf/idpf_lib.c | 3 +
drivers/net/ethernet/intel/idpf/idpf_txrx.c | 70 ++++++++++++++++++-
drivers/net/ethernet/intel/idpf/idpf_txrx.h | 2 +
.../net/ethernet/intel/idpf/idpf_virtchnl.c | 56 ++++++++++++++-
drivers/net/ethernet/intel/idpf/virtchnl2.h | 10 +++
6 files changed, 140 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/intel/idpf/idpf.h b/drivers/net/ethernet/intel/idpf/idpf.h
index ec1b75f039bb..03bca27b2724 100644
--- a/drivers/net/ethernet/intel/idpf/idpf.h
+++ b/drivers/net/ethernet/intel/idpf/idpf.h
@@ -720,6 +720,7 @@ struct idpf_adapter {
struct delayed_work stats_task;
struct workqueue_struct *stats_wq;
struct virtchnl2_get_capabilities caps;
+ struct virtchnl2_edt_caps_ilog2 edt_caps;
struct idpf_vc_xn_manager *vcxn_mngr;
struct idpf_dev_ops dev_ops;
diff --git a/drivers/net/ethernet/intel/idpf/idpf_lib.c b/drivers/net/ethernet/intel/idpf/idpf_lib.c
index bb81e620c5c8..106e59dd5fad 100644
--- a/drivers/net/ethernet/intel/idpf/idpf_lib.c
+++ b/drivers/net/ethernet/intel/idpf/idpf_lib.c
@@ -888,6 +888,9 @@ static int idpf_cfg_netdev(struct idpf_vport *vport)
netdev->min_mtu = ETH_MIN_MTU;
netdev->max_mtu = vport->max_mtu;
+ if (adapter->edt_caps.time_horizon_ns)
+ netdev->max_pacing_offload_horizon = adapter->edt_caps.time_horizon_ns;
+
dflt_features = NETIF_F_SG |
NETIF_F_HIGHDMA;
diff --git a/drivers/net/ethernet/intel/idpf/idpf_txrx.c b/drivers/net/ethernet/intel/idpf/idpf_txrx.c
index 99fcd8e298d6..e9068e0a4e5f 100644
--- a/drivers/net/ethernet/intel/idpf/idpf_txrx.c
+++ b/drivers/net/ethernet/intel/idpf/idpf_txrx.c
@@ -1,6 +1,8 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (C) 2023 Intel Corporation */
+#include <linux/netpoll.h>
+
#include "idpf.h"
#include "idpf_ptp.h"
#include "idpf_virtchnl.h"
@@ -2408,7 +2410,12 @@ void idpf_tx_splitq_build_flow_desc(union idpf_tx_flex_desc *desc,
struct idpf_tx_splitq_params *params,
u16 td_cmd, u16 size)
{
- *(u32 *)&desc->flow.qw1.cmd_dtype = (u8)(params->dtype | td_cmd);
+ desc->flow.qw1.cmd_dtype = (u8)(params->dtype | td_cmd);
+
+ desc->flow.qw1.ts[0] = params->offload.desc_ts[0];
+ desc->flow.qw1.ts[1] = params->offload.desc_ts[1];
+ desc->flow.qw1.ts[2] = params->offload.desc_ts[2];
+
desc->flow.qw1.rxr_bufsize = cpu_to_le16((u16)size);
desc->flow.qw1.compl_tag = cpu_to_le16(params->compl_tag);
}
@@ -3011,6 +3018,63 @@ static bool idpf_tx_splitq_need_re(struct idpf_tx_queue *tx_q)
return gap >= IDPF_TX_SPLITQ_RE_MIN_GAP;
}
+static void idpf_tx_splitq_set_txtime(const struct sk_buff *skb,
+ struct idpf_tx_splitq_params *tx_params)
+{
+ struct idpf_netdev_priv *np = netdev_priv(skb->dev);
+ u64 ts, now, horizon;
+
+ horizon = READ_ONCE(skb->dev->pacing_offload_horizon);
+ if (!horizon)
+ return;
+
+ /* Skip if netpoll: not needed and not safe to call ktime helpers */
+ if (netpoll_tx_running(skb->dev))
+ return;
+
+ switch (skb->tstamp_type) {
+ case SKB_CLOCK_REALTIME:
+ ts = ktime_to_ns(ktime_add(skb->tstamp,
+ ktime_mono_to_any(0, TK_OFFS_TAI) -
+ ktime_mono_to_any(0, TK_OFFS_REAL)));
+ break;
+ case SKB_CLOCK_MONOTONIC:
+ ts = ktime_to_ns(ktime_mono_to_any(skb->tstamp, TK_OFFS_TAI));
+ break;
+ case SKB_CLOCK_TAI:
+ ts = ktime_to_ns(skb->tstamp);
+ break;
+ default:
+ WARN_ON_ONCE(1);
+ return;
+ }
+
+ now = ktime_get_clocktai_ns();
+ if (ts < now)
+ return;
+
+ /* beyond offload horizon? set overflow bit only */
+ if (ts > now + horizon) {
+ tx_params->offload.desc_ts[2] =
+ IDPF_TXD_FLOW_SCH_HORIZON_OVERFLOW_M;
+ return;
+ }
+
+ ts >>= np->adapter->edt_caps.tstamp_granularity_pow2;
+
+ /* 0 is valid 23b timestamp, but also means field unset.
+ * Increase by one to avoid this case
+ */
+ if ((ts & 0x7fffff) == 0) {
+ tx_params->offload.desc_ts[0] = 1;
+ return;
+ }
+
+ tx_params->offload.desc_ts[0] = ts & 0xff;
+ tx_params->offload.desc_ts[1] = (ts >> 8) & 0xff;
+ tx_params->offload.desc_ts[2] = ((ts >> 16) & 0x7f);
+}
+
/**
* idpf_tx_splitq_frame - Sends buffer on Tx ring using flex descriptors
* @skb: send buffer
@@ -3097,6 +3161,10 @@ static netdev_tx_t idpf_tx_splitq_frame(struct sk_buff *skb,
tx_params.dtype = IDPF_TX_DESC_DTYPE_FLEX_FLOW_SCHE;
tx_params.eop_cmd = IDPF_TXD_FLEX_FLOW_CMD_EOP;
+
+ if (skb->tstamp)
+ idpf_tx_splitq_set_txtime(skb, &tx_params);
+
/* Set the RE bit periodically to "clean" the descriptor ring */
if (idpf_tx_splitq_need_re(tx_q)) {
tx_params.eop_cmd |= IDPF_TXD_FLEX_FLOW_CMD_RE;
diff --git a/drivers/net/ethernet/intel/idpf/idpf_txrx.h b/drivers/net/ethernet/intel/idpf/idpf_txrx.h
index 908dfa28674e..86e881c697cc 100644
--- a/drivers/net/ethernet/intel/idpf/idpf_txrx.h
+++ b/drivers/net/ethernet/intel/idpf/idpf_txrx.h
@@ -161,6 +161,7 @@ union idpf_tx_flex_desc {
* @tso_segs: Number of segments to be sent
* @tso_hdr_len: Length of headers to be duplicated
* @td_cmd: Command field to be inserted into descriptor
+ * @desc_ts: Flow scheduling offload timestamp
*/
struct idpf_tx_offload_params {
u32 tx_flags;
@@ -174,6 +175,7 @@ struct idpf_tx_offload_params {
u16 tso_hdr_len;
u16 td_cmd;
+ u8 desc_ts[3];
};
/**
diff --git a/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c b/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c
index 8bd6cca64c9b..964eb8aa283f 100644
--- a/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c
+++ b/drivers/net/ethernet/intel/idpf/idpf_virtchnl.c
@@ -2,6 +2,7 @@
/* Copyright (C) 2023 Intel Corporation */
#include <linux/export.h>
+#include <linux/log2.h>
#include <net/libeth/rx.h>
#include "idpf.h"
@@ -1001,7 +1002,8 @@ static int idpf_send_get_caps_msg(struct idpf_adapter *adapter)
VIRTCHNL2_CAP_SPLITQ_QSCHED |
VIRTCHNL2_CAP_PROMISC |
VIRTCHNL2_CAP_LOOPBACK |
- VIRTCHNL2_CAP_PTP);
+ VIRTCHNL2_CAP_PTP |
+ VIRTCHNL2_CAP_EDT);
xn_params.vc_op = VIRTCHNL2_OP_GET_CAPS;
xn_params.send_buf.iov_base = ∩︀
@@ -1019,6 +1021,49 @@ static int idpf_send_get_caps_msg(struct idpf_adapter *adapter)
return 0;
}
+/**
+ * idpf_send_get_edt_caps_msg - Send virtchnl get EDT caps msg
+ * @adapter: Driver specific private struct
+ *
+ * Return: 0 on success or error code on failure.
+ */
+static int idpf_send_get_edt_caps_msg(struct idpf_adapter *adapter)
+{
+ struct virtchnl2_edt_caps caps = {};
+ struct idpf_vc_xn_params xn_params = {
+ .vc_op = VIRTCHNL2_OP_GET_EDT_CAPS,
+ .send_buf = {
+ .iov_base = &caps,
+ .iov_len = sizeof(caps),
+ },
+ .recv_buf = {
+ .iov_base = &caps,
+ .iov_len = sizeof(caps),
+ },
+ .timeout_ms = IDPF_VC_XN_DEFAULT_TIMEOUT_MSEC,
+ };
+ ssize_t reply_sz;
+ u64 gran_ns, horizon_ns;
+
+ reply_sz = idpf_vc_xn_exec(adapter, &xn_params);
+ if (reply_sz < 0)
+ return reply_sz;
+ if (reply_sz < sizeof(caps))
+ return -EIO;
+
+ horizon_ns = le64_to_cpu(caps.time_horizon_ns);
+ if (horizon_ns > U32_MAX) {
+ dev_warn(&adapter->pdev->dev, "EDT horizon exceeds U32\n");
+ return 0;
+ }
+
+ adapter->edt_caps.time_horizon_ns = horizon_ns;
+ gran_ns = le64_to_cpu(caps.tstamp_granularity_ns);
+ adapter->edt_caps.tstamp_granularity_pow2 = gran_ns ? ilog2(gran_ns) : 9;
+
+ return 0;
+}
+
/**
* idpf_send_get_lan_memory_regions - Send virtchnl get LAN memory regions msg
* @adapter: Driver specific private struct
@@ -3547,6 +3592,15 @@ int idpf_vc_core_init(struct idpf_adapter *adapter)
}
}
+ if (idpf_is_cap_ena(adapter, IDPF_OTHER_CAPS, VIRTCHNL2_CAP_EDT)) {
+ err = idpf_send_get_edt_caps_msg(adapter);
+ if (err) {
+ dev_err(&adapter->pdev->dev, "Failed to get EDT caps: %d\n",
+ err);
+ return -EINVAL;
+ }
+ }
+
err = idpf_map_lan_mmio_regs(adapter);
if (err) {
dev_err(&adapter->pdev->dev, "Failed to map BAR0 region(s): %d\n",
diff --git a/drivers/net/ethernet/intel/idpf/virtchnl2.h b/drivers/net/ethernet/intel/idpf/virtchnl2.h
index 39fea65c075c..7525146491cd 100644
--- a/drivers/net/ethernet/intel/idpf/virtchnl2.h
+++ b/drivers/net/ethernet/intel/idpf/virtchnl2.h
@@ -457,6 +457,16 @@ struct virtchnl2_edt_caps {
};
VIRTCHNL2_CHECK_STRUCT_LEN(16, virtchnl2_edt_caps);
+/**
+ * struct virtchnl2_edt_caps_ilog2 - Host parsed EDT caps.
+ * @time_horizon_ns: Total time window in nanoseconds.
+ * @tstamp_granularity_pow2: Log2 of timestamp granularity in nanoseconds.
+ */
+struct virtchnl2_edt_caps_ilog2 {
+ u32 time_horizon_ns;
+ u8 tstamp_granularity_pow2;
+};
+
/**
* struct virtchnl2_version_info - Version information.
* @major: Major version.
--
2.55.0.679.g6767b8d81c-goog
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH net-next v4 4/6] selftests: drv-net: refactor so_txtime errqueue handling
2026-08-06 23:25 [PATCH net-next v4 0/6] hardware pacing offload Willem de Bruijn
` (2 preceding siblings ...)
2026-08-06 23:26 ` [PATCH net-next v4 3/6] idpf: support pacing offload Willem de Bruijn
@ 2026-08-06 23:26 ` Willem de Bruijn
2026-08-06 23:26 ` [PATCH net-next v4 5/6] selftests: drv-net: in so_txtime tell apart sw from hw pacing Willem de Bruijn
` (2 subsequent siblings)
6 siblings, 0 replies; 10+ messages in thread
From: Willem de Bruijn @ 2026-08-06 23:26 UTC (permalink / raw)
To: netdev
Cc: intel-wired-lan, anthony.l.nguyen, joshua.a.hay,
przemyslaw.kitszel, Willem de Bruijn
From: Willem de Bruijn <willemb@google.com>
Prepare error queue handling for upcoming SO_EE_ORIGIN_TIMESTAMPING
messages in the next patch in this series.
Convert do_recv_errqueue_timeout into dispatcher do_recv_errqueue
and move SO_EE_ORIGIN_TXTIME specific code into a separate helper.
This will make the next patch a lot more readable.
No functional changes.
Signed-off-by: Willem de Bruijn <willemb@google.com>
--
Changes
v3 -> v4
- rename func to do_recv_errqueue_txtime
---
.../testing/selftests/drivers/net/so_txtime.c | 66 ++++++++++---------
1 file changed, 36 insertions(+), 30 deletions(-)
diff --git a/tools/testing/selftests/drivers/net/so_txtime.c b/tools/testing/selftests/drivers/net/so_txtime.c
index 55a386f3d1b9..f6f3253ed174 100644
--- a/tools/testing/selftests/drivers/net/so_txtime.c
+++ b/tools/testing/selftests/drivers/net/so_txtime.c
@@ -158,7 +158,40 @@ static void do_recv_verify_empty(int fdr)
error(1, 0, "recv: not empty as expected (%d, %d)", ret, errno);
}
-static int do_recv_errqueue_timeout(int fdt)
+static int do_recv_errqueue_txtime(struct sock_extended_err *err,
+ const char payload_char)
+{
+ const char *reason = NULL;
+ int64_t tstamp = 0;
+
+ switch (err->ee_errno) {
+ case ECANCELED:
+ if (err->ee_code != SO_EE_CODE_TXTIME_MISSED)
+ error(1, 0, "errqueue: unknown ECANCELED %u\n",
+ err->ee_code);
+ reason = "missed txtime";
+ break;
+ case EINVAL:
+ if (err->ee_code != SO_EE_CODE_TXTIME_INVALID_PARAM)
+ error(1, 0, "errqueue: unknown EINVAL %u\n",
+ err->ee_code);
+ reason = "invalid txtime";
+ break;
+ default:
+ error(1, 0, "errqueue: errno %u code %u\n",
+ err->ee_errno, err->ee_code);
+ }
+
+ tstamp = ((int64_t)err->ee_data) << 32 | err->ee_info;
+ tstamp -= (int64_t)glob_tstart;
+ tstamp /= 1000 * 1000;
+ fprintf(stderr, "send: pkt %c at %" PRId64 "ms dropped: %s\n",
+ payload_char, tstamp, reason);
+
+ return 1;
+}
+
+static int do_recv_errqueue(int fdt)
{
char control[CMSG_SPACE(sizeof(struct sock_extended_err)) +
CMSG_SPACE(sizeof(struct sockaddr_in6))] = {0};
@@ -169,7 +202,6 @@ static int do_recv_errqueue_timeout(int fdt)
struct msghdr msg = {0};
struct iovec iov = {0};
struct cmsghdr *cm;
- int64_t tstamp = 0;
iov.iov_base = data;
iov.iov_len = sizeof(data);
@@ -181,8 +213,6 @@ static int do_recv_errqueue_timeout(int fdt)
msg.msg_controllen = sizeof(control);
while (1) {
- const char *reason = NULL;
-
ret = recvmsg(fdt, &msg, MSG_ERRQUEUE);
if (ret == -1 && errno == EAGAIN)
break;
@@ -201,33 +231,9 @@ static int do_recv_errqueue_timeout(int fdt)
if (err->ee_origin != SO_EE_ORIGIN_TXTIME)
error(1, 0, "errqueue: origin 0x%x\n", err->ee_origin);
- switch (err->ee_errno) {
- case ECANCELED:
- if (err->ee_code != SO_EE_CODE_TXTIME_MISSED)
- error(1, 0, "errqueue: unknown ECANCELED %u\n",
- err->ee_code);
- reason = "missed txtime";
- break;
- case EINVAL:
- if (err->ee_code != SO_EE_CODE_TXTIME_INVALID_PARAM)
- error(1, 0, "errqueue: unknown EINVAL %u\n",
- err->ee_code);
- reason = "invalid txtime";
- break;
- default:
- error(1, 0, "errqueue: errno %u code %u\n",
- err->ee_errno, err->ee_code);
- }
-
- tstamp = ((int64_t) err->ee_data) << 32 | err->ee_info;
- tstamp -= (int64_t) glob_tstart;
- tstamp /= 1000 * 1000;
- fprintf(stderr, "send: pkt %c at %" PRId64 "ms dropped: %s\n",
- data[ret - 1], tstamp, reason);
-
+ num_tstamp += do_recv_errqueue_txtime(err, data[ret - 1]);
msg.msg_flags = 0;
msg.msg_controllen = sizeof(control);
- num_tstamp++;
}
return num_tstamp;
@@ -245,7 +251,7 @@ static void recv_errqueue_msgs(int fdt)
error(1, errno, "poll");
if (ret && (pfd.revents & POLLERR))
- num_tstamp += do_recv_errqueue_timeout(fdt);
+ num_tstamp += do_recv_errqueue(fdt);
if (num_tstamp == cfg_num_pkt)
break;
--
2.55.0.679.g6767b8d81c-goog
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH net-next v4 5/6] selftests: drv-net: in so_txtime tell apart sw from hw pacing
2026-08-06 23:25 [PATCH net-next v4 0/6] hardware pacing offload Willem de Bruijn
` (3 preceding siblings ...)
2026-08-06 23:26 ` [PATCH net-next v4 4/6] selftests: drv-net: refactor so_txtime errqueue handling Willem de Bruijn
@ 2026-08-06 23:26 ` Willem de Bruijn
2026-08-06 23:26 ` [PATCH net-next v4 6/6] selftests: drv-net: extend so_txtime with hw offload Willem de Bruijn
2026-08-07 1:18 ` [PATCH net-next v4 0/6] hardware pacing offload Willem de Bruijn
6 siblings, 0 replies; 10+ messages in thread
From: Willem de Bruijn @ 2026-08-06 23:26 UTC (permalink / raw)
To: netdev
Cc: intel-wired-lan, anthony.l.nguyen, joshua.a.hay,
przemyslaw.kitszel, Willem de Bruijn
From: Willem de Bruijn <willemb@google.com>
Detect software pacing in so_txtime.c using SO_TIMESTAMPING.
If '-H' (hw) is passed
1. measure sw tx delay with SOF_TIMESTAMPING_TX_SOFTWARE, and
2. fail if delay exceeds a threshold, indicating pacing
Also extend while condition to account for possible variance.
This applies to all tests, not just the new '-H' variants.
Also reorder getopt parameters to make them alphabetical.
Signed-off-by: Willem de Bruijn <willemb@google.com>
---
Changes
v3 -> v4
- rename -h to -H to avoid aliasing implicit getopt -h
- count sw timestamps to verify that they were actually checked
- (minor) do not ask for SOF_TIMESTAMPING_OPT_ID as not used
- (minor) remove unused parameter
Reading timestamps from the error queue is never particularly slow,
so errors are always counted, not only if !KSFT_MACHINE_SLOW.
---
.../testing/selftests/drivers/net/so_txtime.c | 93 +++++++++++++++----
1 file changed, 77 insertions(+), 16 deletions(-)
diff --git a/tools/testing/selftests/drivers/net/so_txtime.c b/tools/testing/selftests/drivers/net/so_txtime.c
index f6f3253ed174..951312e67b66 100644
--- a/tools/testing/selftests/drivers/net/so_txtime.c
+++ b/tools/testing/selftests/drivers/net/so_txtime.c
@@ -42,8 +42,10 @@ static bool cfg_machine_slow;
static uint64_t cfg_start_time_ns;
static int cfg_mark;
static bool cfg_rx;
+static bool cfg_verify_hw_offload;
static uint64_t glob_tstart;
+static uint64_t glob_tstart_real;
static uint64_t tdeliver_max;
static int errors;
@@ -191,12 +193,32 @@ static int do_recv_errqueue_txtime(struct sock_extended_err *err,
return 1;
}
-static int do_recv_errqueue(int fdt)
+static int do_recv_errqueue_timestamping(struct scm_timestamping *tss)
+{
+ int64_t ts;
+
+ ts = tss->ts[0].tv_sec * 1000ULL * 1000 * 1000;
+ ts += tss->ts[0].tv_nsec;
+ ts -= glob_tstart_real;
+ ts /= 1000;
+
+ if (ts > cfg_variance_us) {
+ fprintf(stderr, "sw delay %" PRId64 "us exceeds bounds\n", ts);
+ if (!cfg_machine_slow)
+ errors++;
+ }
+
+ return 1;
+}
+
+static int do_recv_errqueue(int fdt, int *num_ts)
{
char control[CMSG_SPACE(sizeof(struct sock_extended_err)) +
+ CMSG_SPACE(sizeof(struct scm_timestamping)) +
CMSG_SPACE(sizeof(struct sockaddr_in6))] = {0};
char data[sizeof(struct ethhdr) + sizeof(struct ipv6hdr) +
sizeof(struct udphdr) + 1];
+ struct scm_timestamping *tss;
struct sock_extended_err *err;
int ret, num_tstamp = 0;
struct msghdr msg = {0};
@@ -222,16 +244,29 @@ static int do_recv_errqueue(int fdt)
error(1, 0, "errqueue: flags 0x%x\n", msg.msg_flags);
cm = CMSG_FIRSTHDR(&msg);
+ tss = NULL;
+
+ if (cm->cmsg_level == SOL_SOCKET &&
+ cm->cmsg_type == SCM_TIMESTAMPING) {
+ tss = (void *)CMSG_DATA(cm);
+ cm = CMSG_NXTHDR(&msg, cm);
+ if (!cm)
+ error(1, 0, "timestamp missing ip err\n");
+ }
+
if (cm->cmsg_level != cfg_errq_level ||
cm->cmsg_type != cfg_errq_type)
error(1, 0, "errqueue: type 0x%x.0x%x\n",
cm->cmsg_level, cm->cmsg_type);
err = (struct sock_extended_err *)CMSG_DATA(cm);
- if (err->ee_origin != SO_EE_ORIGIN_TXTIME)
+ if (err->ee_origin == SO_EE_ORIGIN_TXTIME)
+ num_tstamp += do_recv_errqueue_txtime(err, data[ret - 1]);
+ else if (err->ee_origin == SO_EE_ORIGIN_TIMESTAMPING && tss)
+ *num_ts += do_recv_errqueue_timestamping(tss);
+ else
error(1, 0, "errqueue: origin 0x%x\n", err->ee_origin);
- num_tstamp += do_recv_errqueue_txtime(err, data[ret - 1]);
msg.msg_flags = 0;
msg.msg_controllen = sizeof(control);
}
@@ -243,7 +278,7 @@ static void recv_errqueue_msgs(int fdt)
{
struct pollfd pfd = { .fd = fdt, .events = POLLERR };
const int timeout_ms = 10;
- int ret, num_tstamp = 0;
+ int ret, num_tstamp = 0, num_ts = 0;
do {
ret = poll(&pfd, 1, timeout_ms);
@@ -251,12 +286,19 @@ static void recv_errqueue_msgs(int fdt)
error(1, errno, "poll");
if (ret && (pfd.revents & POLLERR))
- num_tstamp += do_recv_errqueue(fdt);
+ num_tstamp += do_recv_errqueue(fdt, &num_ts);
- if (num_tstamp == cfg_num_pkt)
+ if (num_tstamp == cfg_num_pkt || num_ts == cfg_num_pkt)
break;
- } while (gettime_ns(cfg_clockid) < tdeliver_max);
+ } while (gettime_ns(cfg_clockid) <
+ tdeliver_max + (cfg_variance_us * 1000));
+
+ if (cfg_verify_hw_offload && num_ts != cfg_num_pkt) {
+ fprintf(stderr, "missing timestamps: expected %d, got %d\n",
+ cfg_num_pkt, num_ts);
+ errors++;
+ }
}
static void start_time_wait(void)
@@ -301,6 +343,17 @@ static void setsockopt_txtime(int fd)
error(1, 0, "getsockopt txtime: mismatch");
}
+static void setsockopt_txtimestamping(int fd)
+{
+ int val = SOF_TIMESTAMPING_TX_SOFTWARE |
+ SOF_TIMESTAMPING_SOFTWARE |
+ SOF_TIMESTAMPING_OPT_TSONLY;
+
+ if (setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING,
+ &val, sizeof(val)))
+ error(1, errno, "setsockopt timestamping");
+}
+
static int setup_tx(struct sockaddr *addr, socklen_t alen)
{
int fd;
@@ -314,6 +367,9 @@ static int setup_tx(struct sockaddr *addr, socklen_t alen)
setsockopt_txtime(fd);
+ if (cfg_verify_hw_offload)
+ setsockopt_txtimestamping(fd);
+
if (cfg_mark &&
setsockopt(fd, SOL_SOCKET, SO_MARK, &cfg_mark, sizeof(cfg_mark)))
error(1, errno, "setsockopt mark");
@@ -354,6 +410,7 @@ static void do_test_tx(struct sockaddr *addr, socklen_t alen)
start_time_wait();
glob_tstart = gettime_ns(cfg_clockid);
+ glob_tstart_real = gettime_ns(CLOCK_REALTIME);
for (i = 0; i < cfg_num_pkt; i++)
do_send_one(fdt, &cfg_buf[i]);
@@ -446,10 +503,11 @@ static void usage(const char *progname)
" -6 only IPv6\n"
" -c <clock> monotonic or tai (default)\n"
" -D <addr> destination IP address (server)\n"
- " -S <addr> source IP address (client)\n"
+ " -H verify hardware offload (tx)\n"
+ " -m <mark> socket mark\n"
" -r run rx mode\n"
+ " -S <addr> source IP address (client)\n"
" -t <nsec> start time (UTC nanoseconds)\n"
- " -m <mark> socket mark\n"
"\n",
progname);
exit(1);
@@ -461,7 +519,7 @@ static void parse_opts(int argc, char **argv)
int domain = PF_UNSPEC;
int c;
- while ((c = getopt(argc, argv, "46c:S:D:rt:m:")) != -1) {
+ while ((c = getopt(argc, argv, "46c:D:Hm:rS:t:")) != -1) {
switch (c) {
case '4':
if (domain != PF_UNSPEC)
@@ -488,21 +546,24 @@ static void parse_opts(int argc, char **argv)
else
error(1, 0, "unknown clock id %s", optarg);
break;
- case 'S':
- saddr = optarg;
- break;
case 'D':
daddr = optarg;
break;
+ case 'H':
+ cfg_verify_hw_offload = true;
+ break;
+ case 'm':
+ cfg_mark = strtol(optarg, NULL, 0);
+ break;
case 'r':
cfg_rx = true;
break;
+ case 'S':
+ saddr = optarg;
+ break;
case 't':
cfg_start_time_ns = strtoll(optarg, NULL, 0);
break;
- case 'm':
- cfg_mark = strtol(optarg, NULL, 0);
- break;
default:
usage(argv[0]);
}
--
2.55.0.679.g6767b8d81c-goog
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH net-next v4 6/6] selftests: drv-net: extend so_txtime with hw offload
2026-08-06 23:25 [PATCH net-next v4 0/6] hardware pacing offload Willem de Bruijn
` (4 preceding siblings ...)
2026-08-06 23:26 ` [PATCH net-next v4 5/6] selftests: drv-net: in so_txtime tell apart sw from hw pacing Willem de Bruijn
@ 2026-08-06 23:26 ` Willem de Bruijn
2026-08-07 1:18 ` [PATCH net-next v4 0/6] hardware pacing offload Willem de Bruijn
6 siblings, 0 replies; 10+ messages in thread
From: Willem de Bruijn @ 2026-08-06 23:26 UTC (permalink / raw)
To: netdev
Cc: intel-wired-lan, anthony.l.nguyen, joshua.a.hay,
przemyslaw.kitszel, Willem de Bruijn
From: Willem de Bruijn <willemb@google.com>
Add two pacing hardware offload variants
1. one that uses FQ to safely offload when within bounds.
2. one that uses pfifo_fast and thus forwards all packets.
Verify that the packets are paced in hardware with new flag '-H'.
Also increase rcvtimeout significantly to reduce flakiness. Especially
for the new beyond_hw_horizon test, which is close to the 100ms limit.
But update recv_verify_empty to take MSG_DONTWAIT. That last empty
check must not delay each testcase by the receive timeout.
Hardware pacing offload can complete packets out of order. So the
reverse_order test is expected to pass with pfifo_fast too.
Do not test ETF, which does not change its dequeue behavior based on
pacing_offload_horizon.
Signed-off-by: Willem de Bruijn <willemb@google.com>
---
Changes
v3 -> v4
- replace ethtool with rtnetlink APIs
- expect_fail: correctly handle negative test pfifofast beyond_hw_horizon,
also when KSFT_MACHINE_SLOW suppresses timing errors
- commit-msg: clarify that rcvtimeout increase is also needed for
beyond_hw_horizon test
- define the horizon (50ms) once, rather than three times
- leave cfg.require_ipver in place
v2 -> v3
- remove drivers/net/settings timeout change: superseded by recent commit
- add reverse_order comment
v1 -> v2
- re-raise NlError from e (patchwork pylint)
- simplify expect_pass test (patchwork pylint)
---
.../testing/selftests/drivers/net/so_txtime.c | 4 +-
.../selftests/drivers/net/so_txtime.py | 75 ++++++++++++++++++-
2 files changed, 75 insertions(+), 4 deletions(-)
diff --git a/tools/testing/selftests/drivers/net/so_txtime.c b/tools/testing/selftests/drivers/net/so_txtime.c
index 951312e67b66..9028f9e5a411 100644
--- a/tools/testing/selftests/drivers/net/so_txtime.c
+++ b/tools/testing/selftests/drivers/net/so_txtime.c
@@ -155,7 +155,7 @@ static void do_recv_verify_empty(int fdr)
char rbuf[1];
int ret;
- ret = recv(fdr, rbuf, sizeof(rbuf), 0);
+ ret = recv(fdr, rbuf, sizeof(rbuf), MSG_DONTWAIT);
if (ret != -1 || errno != EAGAIN)
error(1, 0, "recv: not empty as expected (%d, %d)", ret, errno);
}
@@ -379,7 +379,7 @@ static int setup_tx(struct sockaddr *addr, socklen_t alen)
static int setup_rx(struct sockaddr *addr, socklen_t alen)
{
- struct timeval tv = { .tv_usec = 100 * 1000 };
+ struct timeval tv = { .tv_usec = 600 * 1000 };
int fd;
fd = socket(addr->sa_family, SOCK_DGRAM, 0);
diff --git a/tools/testing/selftests/drivers/net/so_txtime.py b/tools/testing/selftests/drivers/net/so_txtime.py
index adf6c848d6d8..24a686eda562 100755
--- a/tools/testing/selftests/drivers/net/so_txtime.py
+++ b/tools/testing/selftests/drivers/net/so_txtime.py
@@ -12,7 +12,9 @@ import time
from lib.py import ksft_exit, ksft_run, ksft_variants
from lib.py import KsftNamedVariant, KsftSkipEx
from lib.py import NetDrvEpEnv, bkg, cmd, defer, tc
+from lib.py import RtnlFamily, NlError
+_HW_OFFLOAD_HORIZON_MS = 50
def test_so_txtime(cfg, clockid, ipver, args_tx, args_rx, expect_success):
"""Main function. Run so_txtime as sender and receiver."""
@@ -32,10 +34,39 @@ def test_so_txtime(cfg, clockid, ipver, args_tx, args_rx, expect_success):
expect_fail = not expect_success
if slow_machine:
expect_success = False
+ expect_fail = None
with bkg(cmd_rx, host=cfg.remote, fail=expect_success,
expect_fail=expect_fail, exit_wait=True):
- cmd(cmd_tx)
+ cmd(cmd_tx, fail=expect_success)
+
+
+def _dev_setup_pacing_offload(cfg):
+ """Configure pacing-offload-horizon."""
+ rtnl = RtnlFamily()
+
+ try:
+ link = rtnl.getlink({'ifi-index': cfg.ifindex})
+ except NlError as e:
+ raise KsftSkipEx('getlink not supported by device') from e
+
+ if 'pacing-offload-horizon' not in link or \
+ 'max-pacing-offload-horizon' not in link:
+ raise KsftSkipEx('pacing offload horizon not supported by device')
+
+ horizon = _HW_OFFLOAD_HORIZON_MS * 1000_000
+ if link['max-pacing-offload-horizon'] < horizon:
+ raise KsftSkipEx('pacing offload max horizon too small')
+
+ cur_horizon = link['pacing-offload-horizon']
+ rtnl.setlink({
+ 'ifi-index': cfg.ifindex,
+ 'pacing-offload-horizon': horizon,
+ })
+ defer(rtnl.setlink, {
+ 'ifi-index': cfg.ifindex,
+ 'pacing-offload-horizon': cur_horizon
+ })
def _qdisc_setup(ifname, qdisc, optargs=""):
@@ -56,6 +87,7 @@ def _test_variants_fq():
["one_pkt", "a,10", "a,10"],
["in_order", "a,10,b,20", "a,10,b,20"],
["reverse_order", "a,20,b,10", "b,10,a,20"],
+ ["beyond_hw_horizon", "a,70", "a,70"],
]:
name = f"v{ipver}_{testcase[0]}"
yield KsftNamedVariant(name, ipver, testcase[1], testcase[2])
@@ -69,6 +101,39 @@ def test_so_txtime_fq_mono(cfg, ipver, args_tx, args_rx):
test_so_txtime(cfg, "mono", ipver, args_tx, args_rx, True)
+@ksft_variants(_test_variants_fq())
+def test_so_txtime_fq_mono_hw(cfg, ipver, args_tx, args_rx):
+ """Run all variants of monotonic fq tests, with offload horizon."""
+ cfg.require_ipver(ipver)
+ cfg.require_nsim(nsim_test=False)
+
+ _dev_setup_pacing_offload(cfg)
+ try:
+ _qdisc_setup(cfg.ifname, "fq", f"offload_horizon {_HW_OFFLOAD_HORIZON_MS}ms")
+ except Exception as e:
+ raise KsftSkipEx("netdev does not support offload. skipping") from e
+
+ # Expect all tests to use only hw pacing, except beyond_hw_horizon.
+ # Do not pass -H to that test so that with sw pacing fallback it passes.
+ hw_only = "-H" if args_tx != "a,70" else ""
+ test_so_txtime(cfg, "mono", ipver, f"{hw_only} {args_tx}", args_rx, True)
+
+
+@ksft_variants(_test_variants_fq())
+def test_so_txtime_pfifofast_mono_hw(cfg, ipver, args_tx, args_rx):
+ """Run all variants of monotonic tests, without fq pacing sw backup."""
+ cfg.require_ipver(ipver)
+ cfg.require_nsim(nsim_test=False)
+
+ _dev_setup_pacing_offload(cfg)
+ _qdisc_setup(cfg.ifname, "pfifo_fast")
+
+ # Expect all tests to pass, except beyond_hw_horizon without sw fallback.
+ # It will send immediately, failing the receiver arrival bounds check.
+ expect_pass = not args_tx == "a,70"
+ test_so_txtime(cfg, "mono", ipver, f"-H {args_tx}", args_rx, expect_pass)
+
+
@ksft_variants(_test_variants_fq())
def test_so_txtime_fq_tai(cfg, ipver, args_tx, args_rx):
"""Run all variants of fq tests, but pass CLOCK_TAI to test conversion."""
@@ -108,7 +173,13 @@ def main() -> None:
"""Boilerplate ksft main."""
with NetDrvEpEnv(__file__) as cfg:
ksft_run(
- [test_so_txtime_fq_mono, test_so_txtime_fq_tai, test_so_txtime_etf],
+ [
+ test_so_txtime_fq_mono,
+ test_so_txtime_fq_mono_hw,
+ test_so_txtime_pfifofast_mono_hw,
+ test_so_txtime_fq_tai,
+ test_so_txtime_etf,
+ ],
args=(cfg,),
)
ksft_exit()
--
2.55.0.679.g6767b8d81c-goog
^ permalink raw reply related [flat|nested] 10+ messages in thread* Re: [PATCH net-next v4 0/6] hardware pacing offload
2026-08-06 23:25 [PATCH net-next v4 0/6] hardware pacing offload Willem de Bruijn
` (5 preceding siblings ...)
2026-08-06 23:26 ` [PATCH net-next v4 6/6] selftests: drv-net: extend so_txtime with hw offload Willem de Bruijn
@ 2026-08-07 1:18 ` Willem de Bruijn
2026-08-07 22:39 ` Jakub Kicinski
6 siblings, 1 reply; 10+ messages in thread
From: Willem de Bruijn @ 2026-08-07 1:18 UTC (permalink / raw)
To: Willem de Bruijn, netdev
Cc: intel-wired-lan, anthony.l.nguyen, joshua.a.hay,
przemyslaw.kitszel, Willem de Bruijn
Willem de Bruijn wrote:
> From: Willem de Bruijn <willemb@google.com>
>
> Enable safe hardware pacing offload on a first device (idpf), plus support.
>
> - Add rtnetlink support for admin control over the device feature
> - Add device support to the idpf driver
>
> Besides the main feature, also
>
> - Small optimization to FQ offload: avoid unnecessary ktime_.._get().
> - Expand so_txtime drv-net test with hw offload testcases.
> - Expand pktgen with txtime feature.
>
> Details and detailed changelog in the individual patches.
>
> High level changes:
> v3 -> v4
> - Replace ethtool API with rtnetlink
> - Drop/defer pktgen patch: not essential
>
> NOTE: the idpf patch will have a minor merge conflict with commit
> "idpf: add missing cpu_to_le32 in idpf_tx_splitq_build_flow_desc"
> (https://lore.kernel.org/netdev/20260803210707.1912217-1-willemdebruijn.kernel@gmail.com/).
> I expect that this series will need another revision, at which point
> that fix will have made its way to net-next. There is also a fix to
> so_txtime.py outstanding, but that is structured so that these do not
> conflict.
>
> v3: https://lore.kernel.org/netdev/20260726202902.760552-1-willemdebruijn.kernel@gmail.com/
> v2: https://lore.kernel.org/netdev/20260722204454.3234605-1-willemdebruijn.kernel@gmail.com/
> v1: https://lore.kernel.org/netdev/20260706133433.3142805-1-willemdebruijn.kernel@gmail.com/
>
> Willem de Bruijn (6):
> net: rtnetlink: add pacing_offload_horizon attribute to net_device
> net_sched: sch_fq: clear past skb->tstamp if offloading pacing
> idpf: support pacing offload
> selftests: drv-net: refactor so_txtime errqueue handling
> selftests: drv-net: in so_txtime tell apart sw from hw pacing
> selftests: drv-net: extend so_txtime with hw offload
>
> Documentation/netlink/specs/rt-link.yaml | 6 +
> .../networking/net_cachelines/net_device.rst | 3 +-
> drivers/net/ethernet/intel/idpf/idpf.h | 1 +
> drivers/net/ethernet/intel/idpf/idpf_lib.c | 3 +
> drivers/net/ethernet/intel/idpf/idpf_txrx.c | 70 +++++++-
> drivers/net/ethernet/intel/idpf/idpf_txrx.h | 2 +
> .../net/ethernet/intel/idpf/idpf_virtchnl.c | 56 ++++++-
> drivers/net/ethernet/intel/idpf/virtchnl2.h | 10 ++
> include/linux/netdevice.h | 4 +-
> include/linux/skbuff.h | 17 +-
> include/uapi/linux/if_link.h | 1 +
> net/core/rtnetlink.c | 21 +++
> net/sched/sch_fq.c | 19 ++-
> .../testing/selftests/drivers/net/so_txtime.c | 157 +++++++++++++-----
> .../selftests/drivers/net/so_txtime.py | 75 ++++++++-
> 15 files changed, 385 insertions(+), 60 deletions(-)
This was intended to have a wider audience list. Will address in v5.
^ permalink raw reply [flat|nested] 10+ messages in thread