* [PATCH net-next 0/7] hardware pacing offload
@ 2026-07-06 13:34 Willem de Bruijn
2026-07-06 13:34 ` [PATCH net-next 1/7] net: ethtool: add hardware pacing offload support to rings Willem de Bruijn
` (6 more replies)
0 siblings, 7 replies; 8+ messages in thread
From: Willem de Bruijn @ 2026-07-06 13:34 UTC (permalink / raw)
To: netdev; +Cc: davem, kuba, edumazet, pabeni, horms, andrew, Willem de Bruijn
From: Willem de Bruijn <willemb@google.com>
Enable safe hardware pacing offload on a first device (idpf), plus support.
- Add ethtool 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 in the individual patches.
Willem de Bruijn (7):
net: ethtool: add hardware pacing offload support to rings
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
net: pktgen: add support for SO_TXTIME
Documentation/netlink/specs/ethtool.yaml | 6 +
.../networking/net_cachelines/net_device.rst | 3 +-
.../net/ethernet/intel/idpf/idpf_ethtool.c | 3 +-
drivers/net/ethernet/intel/idpf/idpf_lib.c | 5 +
drivers/net/ethernet/intel/idpf/idpf_txrx.c | 63 +++++++-
drivers/net/ethernet/intel/idpf/idpf_txrx.h | 2 +
include/linux/ethtool.h | 6 +
include/linux/netdevice.h | 6 +-
.../uapi/linux/ethtool_netlink_generated.h | 2 +
net/core/pktgen.c | 85 ++++++++++
net/core/rtnetlink.c | 2 +-
net/ethtool/common.c | 3 +
net/ethtool/netlink.h | 2 +-
net/ethtool/rings.c | 30 +++-
net/sched/sch_fq.c | 21 ++-
tools/testing/selftests/drivers/net/settings | 1 +
.../testing/selftests/drivers/net/so_txtime.c | 147 +++++++++++++-----
.../selftests/drivers/net/so_txtime.py | 72 ++++++++-
18 files changed, 399 insertions(+), 60 deletions(-)
create mode 100644 tools/testing/selftests/drivers/net/settings
--
2.55.0.795.g602f6c329a-goog
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH net-next 1/7] net: ethtool: add hardware pacing offload support to rings
2026-07-06 13:34 [PATCH net-next 0/7] hardware pacing offload Willem de Bruijn
@ 2026-07-06 13:34 ` Willem de Bruijn
2026-07-06 13:34 ` [PATCH net-next 2/7] net_sched: sch_fq: clear past skb->tstamp if offloading pacing Willem de Bruijn
` (5 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Willem de Bruijn @ 2026-07-06 13:34 UTC (permalink / raw)
To: netdev; +Cc: davem, kuba, edumazet, pabeni, horms, andrew, Willem de Bruijn
From: Willem de Bruijn <willemb@google.com>
Add two ethtool rings operations:
- get pacing offload horizon: active and max
- set pacing offload horizon: active
Replace u64 max_pacing_offload_horizon with u32 active and max fields.
This occupies the same original 8 bytes. Reduce precision from nsec to
usec, which is sufficient.
Update the FQ scheduler to test against the active limit instead of
max. It is the administrator responsibility to set the active limit
before installing FQ.
Assisted-by: Gemini:gemini-3
Signed-off-by: Willem de Bruijn <willemb@google.com>
---
Documentation/netlink/specs/ethtool.yaml | 6 ++++
.../networking/net_cachelines/net_device.rst | 3 +-
include/linux/ethtool.h | 6 ++++
include/linux/netdevice.h | 6 ++--
.../uapi/linux/ethtool_netlink_generated.h | 2 ++
net/core/rtnetlink.c | 2 +-
net/ethtool/common.c | 3 ++
net/ethtool/netlink.h | 2 +-
net/ethtool/rings.c | 30 +++++++++++++++++--
net/sched/sch_fq.c | 3 +-
10 files changed, 55 insertions(+), 8 deletions(-)
diff --git a/Documentation/netlink/specs/ethtool.yaml b/Documentation/netlink/specs/ethtool.yaml
index 5dd4d1b5d94b..48f43dda2e6e 100644
--- a/Documentation/netlink/specs/ethtool.yaml
+++ b/Documentation/netlink/specs/ethtool.yaml
@@ -448,6 +448,12 @@ attribute-sets:
-
name: hds-thresh-max
type: u32
+ -
+ name: pacing-offload-horizon
+ type: u32
+ -
+ name: pacing-offload-horizon-max
+ type: u32
-
name: mm-stat
diff --git a/Documentation/networking/net_cachelines/net_device.rst b/Documentation/networking/net_cachelines/net_device.rst
index 512f6d6fa3d8..3b9ce1c6e105 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 pacing_offload_horizon
+u32 max_pacing_offload_horizon
struct_napi_config* napi_config
unsigned_long gro_flush_timeout
u32 napi_defer_hard_irqs
diff --git a/include/linux/ethtool.h b/include/linux/ethtool.h
index 5d491a98265e..5759be3fcd7f 100644
--- a/include/linux/ethtool.h
+++ b/include/linux/ethtool.h
@@ -85,6 +85,8 @@ enum {
* @tx_push_buf_max_len: Maximum allowed size of TX push buffer
* @hds_thresh: Packet size threshold for header data split (HDS)
* @hds_thresh_max: Maximum supported setting for @hds_threshold
+ * @pacing_offload_horizon: pacing offload horizon value in usec.
+ * @max_pacing_offload_horizon: max pacing offload horizon value in usec.
*
*/
struct kernel_ethtool_ringparam {
@@ -97,6 +99,8 @@ struct kernel_ethtool_ringparam {
u32 tx_push_buf_max_len;
u32 hds_thresh;
u32 hds_thresh_max;
+ u32 pacing_offload_horizon;
+ u32 max_pacing_offload_horizon;
};
/**
@@ -108,6 +112,7 @@ struct kernel_ethtool_ringparam {
* @ETHTOOL_RING_USE_TX_PUSH_BUF_LEN: capture for setting tx_push_buf_len
* @ETHTOOL_RING_USE_TCP_DATA_SPLIT: capture for setting tcp_data_split
* @ETHTOOL_RING_USE_HDS_THRS: capture for setting header-data-split-thresh
+ * @ETHTOOL_RING_USE_PACING_OFFLOAD_HORIZON: capture for setting pacing offload horizon
*/
enum ethtool_supported_ring_param {
ETHTOOL_RING_USE_RX_BUF_LEN = BIT(0),
@@ -117,6 +122,7 @@ enum ethtool_supported_ring_param {
ETHTOOL_RING_USE_TX_PUSH_BUF_LEN = BIT(4),
ETHTOOL_RING_USE_TCP_DATA_SPLIT = BIT(5),
ETHTOOL_RING_USE_HDS_THRS = BIT(6),
+ ETHTOOL_RING_USE_PACING_OFFLOAD_HORIZON = BIT(7),
};
#define __ETH_RSS_HASH_BIT(bit) ((u32)1 << (bit))
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index 9981d637f8b5..cf777a58b328 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -2127,7 +2127,8 @@ enum netdev_reg_state {
* @dpll_pin: Pointer to the SyncE source pin of a DPLL subsystem,
* where the clock is recovered.
*
- * @max_pacing_offload_horizon: max EDT offload horizon in nsec.
+ * @pacing_offload_horizon: active pacing offload horizon in usec.
+ * @max_pacing_offload_horizon: max pacing offload horizon in usec.
* @napi_config: An array of napi_config structures containing per-NAPI
* settings.
* @num_napi_configs: number of allocated NAPI config structs,
@@ -2547,7 +2548,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 pacing_offload_horizon;
+ u32 max_pacing_offload_horizon;
struct napi_config *napi_config;
u32 num_napi_configs;
u32 napi_defer_hard_irqs;
diff --git a/include/uapi/linux/ethtool_netlink_generated.h b/include/uapi/linux/ethtool_netlink_generated.h
index 8134baf7860f..6f1a2fa36157 100644
--- a/include/uapi/linux/ethtool_netlink_generated.h
+++ b/include/uapi/linux/ethtool_netlink_generated.h
@@ -193,6 +193,8 @@ enum {
ETHTOOL_A_RINGS_TX_PUSH_BUF_LEN_MAX,
ETHTOOL_A_RINGS_HDS_THRESH,
ETHTOOL_A_RINGS_HDS_THRESH_MAX,
+ ETHTOOL_A_RINGS_PACING_OFFLOAD_HORIZON,
+ ETHTOOL_A_RINGS_PACING_OFFLOAD_HORIZON_MAX,
__ETHTOOL_A_RINGS_CNT,
ETHTOOL_A_RINGS_MAX = (__ETHTOOL_A_RINGS_CNT - 1)
diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c
index 12aa3aa1688b..efcb219561ea 100644
--- a/net/core/rtnetlink.c
+++ b/net/core/rtnetlink.c
@@ -2124,7 +2124,7 @@ static int rtnl_fill_ifinfo(struct sk_buff *skb,
nla_put_u32(skb, IFLA_TSO_MAX_SEGS,
READ_ONCE(dev->tso_max_segs)) ||
nla_put_uint(skb, IFLA_MAX_PACING_OFFLOAD_HORIZON,
- READ_ONCE(dev->max_pacing_offload_horizon)) ||
+ (u64)READ_ONCE(dev->max_pacing_offload_horizon) * NSEC_PER_USEC) ||
#ifdef CONFIG_RPS
nla_put_u32(skb, IFLA_NUM_RX_QUEUES,
READ_ONCE(dev->num_rx_queues)) ||
diff --git a/net/ethtool/common.c b/net/ethtool/common.c
index 23db40618fed..5c2323aaa02b 100644
--- a/net/ethtool/common.c
+++ b/net/ethtool/common.c
@@ -954,6 +954,9 @@ void ethtool_ringparam_get_cfg(struct net_device *dev,
/* Driver gives us current state, we want to return current config */
kparam->tcp_data_split = dev->cfg->hds_config;
kparam->hds_thresh = dev->cfg->hds_thresh;
+
+ kparam->pacing_offload_horizon = dev->pacing_offload_horizon;
+ kparam->max_pacing_offload_horizon = dev->max_pacing_offload_horizon;
}
static void ethtool_init_tsinfo(struct kernel_ethtool_ts_info *info)
diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h
index 4ca2eca2e94b..fb0d99fd8cfd 100644
--- a/net/ethtool/netlink.h
+++ b/net/ethtool/netlink.h
@@ -466,7 +466,7 @@ extern const struct nla_policy ethnl_features_set_policy[ETHTOOL_A_FEATURES_WANT
extern const struct nla_policy ethnl_privflags_get_policy[ETHTOOL_A_PRIVFLAGS_HEADER + 1];
extern const struct nla_policy ethnl_privflags_set_policy[ETHTOOL_A_PRIVFLAGS_FLAGS + 1];
extern const struct nla_policy ethnl_rings_get_policy[ETHTOOL_A_RINGS_HEADER + 1];
-extern const struct nla_policy ethnl_rings_set_policy[ETHTOOL_A_RINGS_HDS_THRESH_MAX + 1];
+extern const struct nla_policy ethnl_rings_set_policy[ETHTOOL_A_RINGS_PACING_OFFLOAD_HORIZON_MAX + 1];
extern const struct nla_policy ethnl_channels_get_policy[ETHTOOL_A_CHANNELS_HEADER + 1];
extern const struct nla_policy ethnl_channels_set_policy[ETHTOOL_A_CHANNELS_COMBINED_COUNT + 1];
extern const struct nla_policy ethnl_coalesce_get_policy[ETHTOOL_A_COALESCE_HEADER + 1];
diff --git a/net/ethtool/rings.c b/net/ethtool/rings.c
index 9054c89c5d7b..3ff0cb57d03c 100644
--- a/net/ethtool/rings.c
+++ b/net/ethtool/rings.c
@@ -42,6 +42,8 @@ static int rings_prepare_data(const struct ethnl_req_info *req_base,
data->kernel_ringparam.tcp_data_split = dev->cfg->hds_config;
data->kernel_ringparam.hds_thresh = dev->cfg->hds_thresh;
+ data->kernel_ringparam.pacing_offload_horizon = dev->pacing_offload_horizon;
+ data->kernel_ringparam.max_pacing_offload_horizon = dev->max_pacing_offload_horizon;
dev->ethtool_ops->get_ringparam(dev, &data->ringparam,
&data->kernel_ringparam, info->extack);
@@ -69,7 +71,9 @@ static int rings_reply_size(const struct ethnl_req_info *req_base,
nla_total_size(sizeof(u32)) + /* _RINGS_TX_PUSH_BUF_LEN */
nla_total_size(sizeof(u32)) + /* _RINGS_TX_PUSH_BUF_LEN_MAX */
nla_total_size(sizeof(u32)) + /* _RINGS_HDS_THRESH */
- nla_total_size(sizeof(u32)); /* _RINGS_HDS_THRESH_MAX*/
+ nla_total_size(sizeof(u32)) + /* _RINGS_HDS_THRESH_MAX*/
+ nla_total_size(sizeof(u32)) + /* _RINGS_PACING_OFFLOAD_HORIZON */
+ nla_total_size(sizeof(u32)); /* _RINGS_PACING_OFFLOAD_HORIZON_MAX */
}
static int rings_fill_reply(struct sk_buff *skb,
@@ -121,7 +125,12 @@ static int rings_fill_reply(struct sk_buff *skb,
(nla_put_u32(skb, ETHTOOL_A_RINGS_HDS_THRESH,
kr->hds_thresh) ||
nla_put_u32(skb, ETHTOOL_A_RINGS_HDS_THRESH_MAX,
- kr->hds_thresh_max))))
+ kr->hds_thresh_max))) ||
+ ((supported_ring_params & ETHTOOL_RING_USE_PACING_OFFLOAD_HORIZON) &&
+ (nla_put_u32(skb, ETHTOOL_A_RINGS_PACING_OFFLOAD_HORIZON,
+ kr->pacing_offload_horizon) ||
+ nla_put_u32(skb, ETHTOOL_A_RINGS_PACING_OFFLOAD_HORIZON_MAX,
+ kr->max_pacing_offload_horizon))))
return -EMSGSIZE;
return 0;
@@ -144,6 +153,7 @@ const struct nla_policy ethnl_rings_set_policy[] = {
[ETHTOOL_A_RINGS_RX_PUSH] = NLA_POLICY_MAX(NLA_U8, 1),
[ETHTOOL_A_RINGS_TX_PUSH_BUF_LEN] = { .type = NLA_U32 },
[ETHTOOL_A_RINGS_HDS_THRESH] = { .type = NLA_U32 },
+ [ETHTOOL_A_RINGS_PACING_OFFLOAD_HORIZON] = { .type = NLA_U32 },
};
static int
@@ -177,6 +187,14 @@ ethnl_set_rings_validate(struct ethnl_req_info *req_info,
return -EOPNOTSUPP;
}
+ if (tb[ETHTOOL_A_RINGS_PACING_OFFLOAD_HORIZON] &&
+ !(ops->supported_ring_params & ETHTOOL_RING_USE_PACING_OFFLOAD_HORIZON)) {
+ NL_SET_ERR_MSG_ATTR(info->extack,
+ tb[ETHTOOL_A_RINGS_PACING_OFFLOAD_HORIZON],
+ "setting pacing offload horizon is not supported");
+ return -EOPNOTSUPP;
+ }
+
if (tb[ETHTOOL_A_RINGS_CQE_SIZE] &&
!(ops->supported_ring_params & ETHTOOL_RING_USE_CQE_SIZE)) {
NL_SET_ERR_MSG_ATTR(info->extack,
@@ -246,6 +264,8 @@ ethnl_set_rings(struct ethnl_req_info *req_info, struct genl_info *info)
tb[ETHTOOL_A_RINGS_TX_PUSH_BUF_LEN], &mod);
ethnl_update_u32(&kernel_ringparam.hds_thresh,
tb[ETHTOOL_A_RINGS_HDS_THRESH], &mod);
+ ethnl_update_u32(&kernel_ringparam.pacing_offload_horizon,
+ tb[ETHTOOL_A_RINGS_PACING_OFFLOAD_HORIZON], &mod);
if (!mod)
return 0;
@@ -281,6 +301,9 @@ ethnl_set_rings(struct ethnl_req_info *req_info, struct genl_info *info)
err_attr = tb[ETHTOOL_A_RINGS_TX];
else if (kernel_ringparam.hds_thresh > kernel_ringparam.hds_thresh_max)
err_attr = tb[ETHTOOL_A_RINGS_HDS_THRESH];
+ else if (kernel_ringparam.pacing_offload_horizon >
+ kernel_ringparam.max_pacing_offload_horizon)
+ err_attr = tb[ETHTOOL_A_RINGS_PACING_OFFLOAD_HORIZON];
else
err_attr = NULL;
if (err_attr) {
@@ -302,6 +325,9 @@ ethnl_set_rings(struct ethnl_req_info *req_info, struct genl_info *info)
ret = dev->ethtool_ops->set_ringparam(dev, &ringparam,
&kernel_ringparam, info->extack);
+ if (!ret)
+ dev->pacing_offload_horizon = kernel_ringparam.pacing_offload_horizon;
+
return ret < 0 ? ret : 1;
}
diff --git a/net/sched/sch_fq.c b/net/sched/sch_fq.c
index 7cae082a9847..d4cc8bc9fa06 100644
--- a/net/sched/sch_fq.c
+++ b/net/sched/sch_fq.c
@@ -1179,7 +1179,8 @@ static int fq_change(struct Qdisc *sch, struct nlattr *opt,
u64 offload_horizon = (u64)NSEC_PER_USEC *
nla_get_u32(tb[TCA_FQ_OFFLOAD_HORIZON]);
- if (offload_horizon <= qdisc_dev(sch)->max_pacing_offload_horizon) {
+ if (offload_horizon <=
+ (u64)qdisc_dev(sch)->pacing_offload_horizon * NSEC_PER_USEC) {
WRITE_ONCE(q->offload_horizon, offload_horizon);
} else {
NL_SET_ERR_MSG_MOD(extack, "invalid offload_horizon");
--
2.55.0.795.g602f6c329a-goog
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH net-next 2/7] net_sched: sch_fq: clear past skb->tstamp if offloading pacing
2026-07-06 13:34 [PATCH net-next 0/7] hardware pacing offload Willem de Bruijn
2026-07-06 13:34 ` [PATCH net-next 1/7] net: ethtool: add hardware pacing offload support to rings Willem de Bruijn
@ 2026-07-06 13:34 ` Willem de Bruijn
2026-07-06 13:34 ` [PATCH net-next 3/7] idpf: support pacing offload Willem de Bruijn
` (4 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Willem de Bruijn @ 2026-07-06 13:34 UTC (permalink / raw)
To: netdev; +Cc: davem, kuba, edumazet, pabeni, horms, andrew, 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>
---
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.
---
net/sched/sch_fq.c | 18 ++++++++++++++----
1 file changed, 14 insertions(+), 4 deletions(-)
diff --git a/net/sched/sch_fq.c b/net/sched/sch_fq.c
index d4cc8bc9fa06..36006f69a875 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->tstamp = 0;
+
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;
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,15 @@ 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 <= now)
+ skb->tstamp = 0;
+
return skb;
}
--
2.55.0.795.g602f6c329a-goog
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH net-next 3/7] idpf: support pacing offload
2026-07-06 13:34 [PATCH net-next 0/7] hardware pacing offload Willem de Bruijn
2026-07-06 13:34 ` [PATCH net-next 1/7] net: ethtool: add hardware pacing offload support to rings Willem de Bruijn
2026-07-06 13:34 ` [PATCH net-next 2/7] net_sched: sch_fq: clear past skb->tstamp if offloading pacing Willem de Bruijn
@ 2026-07-06 13:34 ` Willem de Bruijn
2026-07-06 13:34 ` [PATCH net-next 4/7] selftests: drv-net: refactor so_txtime errqueue handling Willem de Bruijn
` (3 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Willem de Bruijn @ 2026-07-06 13:34 UTC (permalink / raw)
To: netdev
Cc: davem, kuba, edumazet, pabeni, horms, andrew, Willem de Bruijn,
Tony Nguyen, Joshua A Hay
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.
Bound check txtime. Only packets with timestamp between now and the
horizon (max_pacing_offload_horizon) are offloaded.
Support only in splitq mode, where tx and tx completion queues are
separate and so completions can be returned out of order.
Assume that the NIC clock is PTP synchronized to CLOCK_TAI. This
can later be refined, e.g., to a custom CLOCK_AUX.
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: Joshua A Hay <joshua.a.hay@intel.com>
Signed-off-by: Willem de Bruijn <willemb@google.com>
---
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.
---
.../net/ethernet/intel/idpf/idpf_ethtool.c | 3 +-
drivers/net/ethernet/intel/idpf/idpf_lib.c | 5 ++
drivers/net/ethernet/intel/idpf/idpf_txrx.c | 63 ++++++++++++++++++-
drivers/net/ethernet/intel/idpf/idpf_txrx.h | 2 +
4 files changed, 71 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/intel/idpf/idpf_ethtool.c b/drivers/net/ethernet/intel/idpf/idpf_ethtool.c
index bb99d9e7c65d..5ddf347927dc 100644
--- a/drivers/net/ethernet/intel/idpf/idpf_ethtool.c
+++ b/drivers/net/ethernet/intel/idpf/idpf_ethtool.c
@@ -1801,7 +1801,8 @@ static void idpf_get_ts_stats(struct net_device *netdev,
static const struct ethtool_ops idpf_ethtool_ops = {
.supported_coalesce_params = ETHTOOL_COALESCE_USECS |
ETHTOOL_COALESCE_USE_ADAPTIVE,
- .supported_ring_params = ETHTOOL_RING_USE_TCP_DATA_SPLIT,
+ .supported_ring_params = ETHTOOL_RING_USE_TCP_DATA_SPLIT |
+ ETHTOOL_RING_USE_PACING_OFFLOAD_HORIZON,
.get_msglevel = idpf_get_msglevel,
.set_msglevel = idpf_set_msglevel,
.get_link = ethtool_op_get_link,
diff --git a/drivers/net/ethernet/intel/idpf/idpf_lib.c b/drivers/net/ethernet/intel/idpf/idpf_lib.c
index cf966fe6c759..de4170c2aa3b 100644
--- a/drivers/net/ethernet/intel/idpf/idpf_lib.c
+++ b/drivers/net/ethernet/intel/idpf/idpf_lib.c
@@ -888,6 +888,11 @@ static int idpf_cfg_netdev(struct idpf_vport *vport)
netdev->min_mtu = ETH_MIN_MTU;
netdev->max_mtu = vport->max_mtu;
+ if (idpf_is_queue_model_split(vport->dflt_qv_rsrc.txq_model) &&
+ !idpf_is_cap_ena(adapter, IDPF_OTHER_CAPS,
+ VIRTCHNL2_CAP_SPLITQ_QSCHED))
+ netdev->max_pacing_offload_horizon = 128000;
+
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 7f9056404f64..3de0c4ec54f6 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,9 +2410,13 @@ 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);
+ *(__le32 *)&desc->flow.qw1.cmd_dtype = cpu_to_le32((u8)(params->dtype | td_cmd));
desc->flow.qw1.rxr_bufsize = cpu_to_le16((u16)size);
desc->flow.qw1.compl_tag = cpu_to_le16(params->compl_tag);
+
+ 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];
}
/**
@@ -3011,6 +3017,57 @@ 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 idpf_tx_queue *tx_q,
+ const struct sk_buff *skb,
+ struct idpf_tx_splitq_params *tx_params)
+{
+ const int ts_gran_pow2 = 9;
+ u64 ts, now;
+
+ /* 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 + ((u64)skb->dev->pacing_offload_horizon * NSEC_PER_USEC)) {
+ tx_params->offload.desc_ts[2] = 1 << 7;
+ return;
+ }
+
+ ts >>= ts_gran_pow2;
+
+ 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);
+
+ /* 0 is valid 24b timestamp, but also means field unset.
+ * Add 512 ns (ts_gran_pow2) to avoid this case
+ */
+ if ((ts & 0x7fffff) == 0)
+ tx_params->offload.desc_ts[0] = 1;
+}
+
/**
* idpf_tx_splitq_frame - Sends buffer on Tx ring using flex descriptors
* @skb: send buffer
@@ -3097,6 +3154,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 && skb->dev->pacing_offload_horizon)
+ idpf_tx_splitq_set_txtime(tx_q, skb, &tx_params);
+
/* Set the RE bit to periodically "clean" the descriptor ring.
* MIN_GAP is set to MIN_RING size to ensure it will be set at
* least once each time around the ring.
diff --git a/drivers/net/ethernet/intel/idpf/idpf_txrx.h b/drivers/net/ethernet/intel/idpf/idpf_txrx.h
index 4be5b3b6d3ed..865408b57b0d 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];
};
/**
--
2.55.0.795.g602f6c329a-goog
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH net-next 4/7] selftests: drv-net: refactor so_txtime errqueue handling
2026-07-06 13:34 [PATCH net-next 0/7] hardware pacing offload Willem de Bruijn
` (2 preceding siblings ...)
2026-07-06 13:34 ` [PATCH net-next 3/7] idpf: support pacing offload Willem de Bruijn
@ 2026-07-06 13:34 ` Willem de Bruijn
2026-07-06 13:34 ` [PATCH net-next 5/7] selftests: drv-net: in so_txtime tell apart sw from hw pacing Willem de Bruijn
` (2 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Willem de Bruijn @ 2026-07-06 13:34 UTC (permalink / raw)
To: netdev; +Cc: davem, kuba, edumazet, pabeni, horms, andrew, 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>
---
.../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..7364ce2bec0b 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_timeout(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_timeout(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.795.g602f6c329a-goog
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH net-next 5/7] selftests: drv-net: in so_txtime tell apart sw from hw pacing
2026-07-06 13:34 [PATCH net-next 0/7] hardware pacing offload Willem de Bruijn
` (3 preceding siblings ...)
2026-07-06 13:34 ` [PATCH net-next 4/7] selftests: drv-net: refactor so_txtime errqueue handling Willem de Bruijn
@ 2026-07-06 13:34 ` Willem de Bruijn
2026-07-06 13:34 ` [PATCH net-next 6/7] selftests: drv-net: extend so_txtime with hw offload Willem de Bruijn
2026-07-06 13:34 ` [PATCH net-next 7/7] net: pktgen: add support for SO_TXTIME Willem de Bruijn
6 siblings, 0 replies; 8+ messages in thread
From: Willem de Bruijn @ 2026-07-06 13:34 UTC (permalink / raw)
To: netdev; +Cc: davem, kuba, edumazet, pabeni, horms, andrew, 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 reorder getopt parameters to make them alphabetical.
Signed-off-by: Willem de Bruijn <willemb@google.com>
---
.../testing/selftests/drivers/net/so_txtime.c | 81 ++++++++++++++++---
1 file changed, 69 insertions(+), 12 deletions(-)
diff --git a/tools/testing/selftests/drivers/net/so_txtime.c b/tools/testing/selftests/drivers/net/so_txtime.c
index 7364ce2bec0b..5900ca97957c 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_timeout(struct sock_extended_err *err,
return 1;
}
+static void do_recv_errqueue_timestamping(struct sock_extended_err *err,
+ struct scm_timestamping *tss)
+{
+ int64_t ts;
+
+ ts = tss->ts[0].tv_sec;
+ ts *= 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++;
+ }
+}
+
static int do_recv_errqueue(int fdt)
{
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,30 @@ 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_timeout(err,
+ data[ret - 1]);
+ else if (err->ee_origin == SO_EE_ORIGIN_TIMESTAMPING && tss)
+ do_recv_errqueue_timestamping(err, tss);
+ else
error(1, 0, "errqueue: origin 0x%x\n", err->ee_origin);
- num_tstamp += do_recv_errqueue_timeout(err, data[ret - 1]);
msg.msg_flags = 0;
msg.msg_controllen = sizeof(control);
}
@@ -256,7 +292,8 @@ static void recv_errqueue_msgs(int fdt)
if (num_tstamp == cfg_num_pkt)
break;
- } while (gettime_ns(cfg_clockid) < tdeliver_max);
+ } while (gettime_ns(cfg_clockid) <
+ tdeliver_max + (cfg_variance_us * 1000));
}
static void start_time_wait(void)
@@ -301,6 +338,18 @@ 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_ID |
+ 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 +363,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 +406,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 +499,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 +515,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 +542,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.795.g602f6c329a-goog
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH net-next 6/7] selftests: drv-net: extend so_txtime with hw offload
2026-07-06 13:34 [PATCH net-next 0/7] hardware pacing offload Willem de Bruijn
` (4 preceding siblings ...)
2026-07-06 13:34 ` [PATCH net-next 5/7] selftests: drv-net: in so_txtime tell apart sw from hw pacing Willem de Bruijn
@ 2026-07-06 13:34 ` Willem de Bruijn
2026-07-06 13:34 ` [PATCH net-next 7/7] net: pktgen: add support for SO_TXTIME Willem de Bruijn
6 siblings, 0 replies; 8+ messages in thread
From: Willem de Bruijn @ 2026-07-06 13:34 UTC (permalink / raw)
To: netdev; +Cc: davem, kuba, edumazet, pabeni, horms, andrew, 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 move the ip version check to the main test_so_txtime callee,
rather than having to add checks to the new variants.
Also increase test timeout to 2 min. This suite now counts 58 tests,
which each take 2 sec to stabilize when run with KSFT_MACHINE_SLOW.
When increasing the bound, do so with a sizable headroom.
Also increase rcvtimeout significantly to reduce flakiness.
Signed-off-by: Willem de Bruijn <willemb@google.com>
---
tools/testing/selftests/drivers/net/settings | 1 +
.../testing/selftests/drivers/net/so_txtime.c | 2 +-
.../selftests/drivers/net/so_txtime.py | 72 +++++++++++++++++--
3 files changed, 70 insertions(+), 5 deletions(-)
create mode 100644 tools/testing/selftests/drivers/net/settings
diff --git a/tools/testing/selftests/drivers/net/settings b/tools/testing/selftests/drivers/net/settings
new file mode 100644
index 000000000000..b478e684846a
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/settings
@@ -0,0 +1 @@
+timeout=240
diff --git a/tools/testing/selftests/drivers/net/so_txtime.c b/tools/testing/selftests/drivers/net/so_txtime.c
index 5900ca97957c..6e64baa4c60d 100644
--- a/tools/testing/selftests/drivers/net/so_txtime.c
+++ b/tools/testing/selftests/drivers/net/so_txtime.c
@@ -375,7 +375,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..9dbf6067de27 100755
--- a/tools/testing/selftests/drivers/net/so_txtime.py
+++ b/tools/testing/selftests/drivers/net/so_txtime.py
@@ -12,10 +12,13 @@ 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 EthtoolFamily, NlError
def test_so_txtime(cfg, clockid, ipver, args_tx, args_rx, expect_success):
"""Main function. Run so_txtime as sender and receiver."""
+ cfg.require_ipver(ipver)
+
slow_machine = os.environ.get('KSFT_MACHINE_SLOW')
if not hasattr(cfg, "bin_remote"):
@@ -38,6 +41,34 @@ def test_so_txtime(cfg, clockid, ipver, args_tx, args_rx, expect_success):
cmd(cmd_tx)
+def _dev_setup_pacing_offload(cfg):
+ """Configure pacing-offload-horizon."""
+ ethnl = EthtoolFamily()
+
+ try:
+ rings = ethnl.rings_get({'header': {'dev-index': cfg.ifindex}})
+ except NlError:
+ raise KsftSkipEx('ring-get not supported by device')
+
+ if 'pacing-offload-horizon' not in rings or \
+ 'pacing-offload-horizon-max' not in rings:
+ raise KsftSkipEx('pacing offload horizon not supported by device')
+
+ if rings['pacing-offload-horizon-max'] < 50_000:
+ raise KsftSkipEx('pacing offload max horizon too small')
+
+ cur_horizon = rings['pacing-offload-horizon']
+ new_horizon = 50_000
+ ethnl.rings_set({
+ 'header': {'dev-index': cfg.ifindex},
+ 'pacing-offload-horizon': new_horizon,
+ })
+ defer(ethnl.rings_set, {
+ 'header': {'dev-index': cfg.ifindex},
+ 'pacing-offload-horizon': cur_horizon
+ })
+
+
def _qdisc_setup(ifname, qdisc, optargs=""):
"""Replace root qdisc. Restore the original after the test.
@@ -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])
@@ -64,15 +96,42 @@ def _test_variants_fq():
@ksft_variants(_test_variants_fq())
def test_so_txtime_fq_mono(cfg, ipver, args_tx, args_rx):
"""Run all variants of monotonic (fq) tests."""
- cfg.require_ipver(ipver)
_qdisc_setup(cfg.ifname, "fq")
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_nsim(nsim_test=False)
+
+ _dev_setup_pacing_offload(cfg)
+ try:
+ _qdisc_setup(cfg.ifname, "fq", "offload_horizon 50ms")
+ 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.
+ 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_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.
+ expect_pass = False if args_tx == "a,70" else True
+ 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."""
- cfg.require_ipver(ipver)
_qdisc_setup(cfg.ifname, "fq")
test_so_txtime(cfg, "tai", ipver, args_tx, args_rx, True)
@@ -95,7 +154,6 @@ def _test_variants_etf():
@ksft_variants(_test_variants_etf())
def test_so_txtime_etf(cfg, ipver, args_tx, args_rx, expect_fail):
"""Run all variants of etf tests."""
- cfg.require_ipver(ipver)
try:
_qdisc_setup(cfg.ifname, "etf", "clockid CLOCK_TAI delta 400000")
except Exception as e:
@@ -108,7 +166,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.795.g602f6c329a-goog
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH net-next 7/7] net: pktgen: add support for SO_TXTIME
2026-07-06 13:34 [PATCH net-next 0/7] hardware pacing offload Willem de Bruijn
` (5 preceding siblings ...)
2026-07-06 13:34 ` [PATCH net-next 6/7] selftests: drv-net: extend so_txtime with hw offload Willem de Bruijn
@ 2026-07-06 13:34 ` Willem de Bruijn
6 siblings, 0 replies; 8+ messages in thread
From: Willem de Bruijn @ 2026-07-06 13:34 UTC (permalink / raw)
To: netdev; +Cc: davem, kuba, edumazet, pabeni, horms, andrew, Willem de Bruijn
From: Willem de Bruijn <willemb@google.com>
Introduce support for setting the SO_TXTIME delivery txtime on
generated packets. This allows testing pacing and scheduling features
in the network stack (e.g., sch_fq, sch_taprio) and drivers.
Add a new flag 'TXTIME' to enable this feature.
Add two new configuration parameters:
- txtime_delay: offset from now in ns
- txtime_clockid: clock to use (monotonic, realtime, tai)
The queue_xmit mode can clear skb->tstamp in-flight, so this must be
used without clone_skb.
Signed-off-by: Willem de Bruijn <willemb@google.com>
---
Sashiko: ignore data races in the control fields. Pktgen requires
superuser privileges, has many such races already, which are benign.
---
net/core/pktgen.c | 85 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 85 insertions(+)
diff --git a/net/core/pktgen.c b/net/core/pktgen.c
index 8e185b318288..be4fce895b2c 100644
--- a/net/core/pktgen.c
+++ b/net/core/pktgen.c
@@ -201,6 +201,7 @@
pf(SVID_RND) /* Random SVLAN ID */ \
pf(NODE) /* Node memory alloc*/ \
pf(SHARED) /* Shared SKB */ \
+ pf(TXTIME) /* SO_TXTIME support */ \
#define pf(flag) flag##_SHIFT,
enum pkt_flags {
@@ -291,6 +292,10 @@ struct pktgen_dev {
struct page *page;
u64 delay; /* nano-seconds */
+ /* TXTIME support */
+ u64 txtime_delay; /* transmit time delay in ns */
+ clockid_t txtime_clockid; /* clockid for SO_TXTIME */
+
__u64 count; /* Default No packets to send */
__u64 sofar; /* How many pkts we've sent so far */
__u64 tx_bytes; /* How many bytes we've transmitted */
@@ -666,6 +671,17 @@ static int pktgen_if_show(struct seq_file *seq, void *v)
if (pkt_dev->node >= 0)
seq_printf(seq, " node: %d\n", pkt_dev->node);
+ if (pkt_dev->flags & F_TXTIME) {
+ clockid_t clockid = READ_ONCE(pkt_dev->txtime_clockid);
+
+ seq_printf(seq, " txtime_delay: %llu\n",
+ (unsigned long long)pkt_dev->txtime_delay);
+ seq_printf(seq, " txtime_clockid: %s\n",
+ clockid == CLOCK_MONOTONIC ? "monotonic" :
+ clockid == CLOCK_REALTIME ? "realtime" :
+ clockid == CLOCK_TAI ? "tai" : "unknown");
+ }
+
if (pkt_dev->xmit_mode == M_NETIF_RECEIVE)
seq_puts(seq, " xmit_mode: netif_receive\n");
else if (pkt_dev->xmit_mode == M_QUEUE_XMIT)
@@ -1141,6 +1157,49 @@ static ssize_t pktgen_if_write(struct file *file,
(unsigned long long) pkt_dev->delay);
return count;
}
+ if (!strcmp(name, "txtime_delay")) {
+ max = min(10, count - i);
+ len = num_arg(&user_buffer[i], max, &value);
+ if (len < 0)
+ return len;
+
+ /* in queue_xmit mode fq may clear tstamp, do not reuse skb */
+ if (value > 0 && pkt_dev->clone_skb > 0)
+ return -EINVAL;
+
+ pkt_dev->txtime_delay = (u64)value;
+ sprintf(pg_result, "OK: txtime_delay=%llu",
+ (unsigned long long)pkt_dev->txtime_delay);
+ return count;
+ }
+ if (!strcmp(name, "txtime_clockid")) {
+ char clockstr[32];
+ clockid_t clk;
+
+ memset(clockstr, 0, sizeof(clockstr));
+ max = min(sizeof(clockstr) - 1, count - i);
+ len = strn_len(&user_buffer[i], max);
+ if (len < 0)
+ return len;
+
+ if (copy_from_user(clockstr, &user_buffer[i], len))
+ return -EFAULT;
+
+ if (!strcmp(clockstr, "monotonic")) {
+ clk = CLOCK_MONOTONIC;
+ } else if (!strcmp(clockstr, "realtime")) {
+ clk = CLOCK_REALTIME;
+ } else if (!strcmp(clockstr, "tai")) {
+ clk = CLOCK_TAI;
+ } else {
+ sprintf(pg_result, "ERROR: unknown clockid '%s'", clockstr);
+ return -EINVAL;
+ }
+
+ sprintf(pg_result, "OK: txtime_clockid=%s", clockstr);
+ WRITE_ONCE(pkt_dev->txtime_clockid, clk);
+ return count;
+ }
if (!strcmp(name, "rate")) {
max = min(10, count - i);
len = num_arg(&user_buffer[i], max, &value);
@@ -1238,6 +1297,8 @@ static ssize_t pktgen_if_write(struct file *file,
if (value > 0 && (pkt_dev->n_imix_entries > 0 ||
!(pkt_dev->flags & F_SHARED)))
return -EINVAL;
+ if (value > 0 && pkt_dev->txtime_delay)
+ return -EINVAL;
pkt_dev->clone_skb = value;
@@ -2321,6 +2382,21 @@ static void pktgen_setup_inject(struct pktgen_dev *pkt_dev)
}
+static ktime_t ktime_get_clock(clockid_t clockid)
+{
+ switch (clockid) {
+ case CLOCK_REALTIME:
+ return ktime_get_real();
+ case CLOCK_MONOTONIC:
+ return ktime_get();
+ case CLOCK_TAI:
+ return ktime_get_clocktai();
+ default:
+ WARN_ON_ONCE(1);
+ return ktime_get();
+ }
+}
+
static void spin(struct pktgen_dev *pkt_dev, ktime_t spin_until)
{
ktime_t start_time, end_time;
@@ -3557,6 +3633,14 @@ static void pktgen_xmit(struct pktgen_dev *pkt_dev)
}
pkt_dev->last_pkt_size = pkt_dev->skb->len;
pkt_dev->clone_count = 0; /* reset counter */
+
+ if (pkt_dev->flags & F_TXTIME && pkt_dev->txtime_delay) {
+ clockid_t clk = READ_ONCE(pkt_dev->txtime_clockid);
+ ktime_t txtime = ktime_add_ns(ktime_get_clock(clk),
+ pkt_dev->txtime_delay);
+
+ skb_set_delivery_type_by_clockid(pkt_dev->skb, txtime, clk);
+ }
}
if (pkt_dev->delay && pkt_dev->last_ok)
@@ -3869,6 +3953,7 @@ static int pktgen_add_device(struct pktgen_thread *t, const char *ifname)
pkt_dev->burst = 1;
pkt_dev->node = NUMA_NO_NODE;
pkt_dev->flags = F_SHARED; /* SKB shared by default */
+ pkt_dev->txtime_clockid = CLOCK_MONOTONIC;
err = pktgen_setup_dev(t->net, pkt_dev, ifname);
if (err)
--
2.55.0.795.g602f6c329a-goog
^ permalink raw reply related [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-07-06 13:40 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-06 13:34 [PATCH net-next 0/7] hardware pacing offload Willem de Bruijn
2026-07-06 13:34 ` [PATCH net-next 1/7] net: ethtool: add hardware pacing offload support to rings Willem de Bruijn
2026-07-06 13:34 ` [PATCH net-next 2/7] net_sched: sch_fq: clear past skb->tstamp if offloading pacing Willem de Bruijn
2026-07-06 13:34 ` [PATCH net-next 3/7] idpf: support pacing offload Willem de Bruijn
2026-07-06 13:34 ` [PATCH net-next 4/7] selftests: drv-net: refactor so_txtime errqueue handling Willem de Bruijn
2026-07-06 13:34 ` [PATCH net-next 5/7] selftests: drv-net: in so_txtime tell apart sw from hw pacing Willem de Bruijn
2026-07-06 13:34 ` [PATCH net-next 6/7] selftests: drv-net: extend so_txtime with hw offload Willem de Bruijn
2026-07-06 13:34 ` [PATCH net-next 7/7] net: pktgen: add support for SO_TXTIME Willem de Bruijn
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox