[PATCH net-next v6 2/6] net_sched: sch_fq: clear past skb->tstamp if offloading pacing
From: Willem de Bruijn <willemdebruijn.kernel@gmail.com>
Date: 2026-08-13 02:05:53
Subsystem:
networking [general], tc subsystem, the rest · Maintainers:
"David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Jamal Hadi Salim, Jiri Pirko, Linus Torvalds
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 clear slightly into the future, for EDT timestamps that are
so close to now that they fall within a reasonable normal Tx
latency. This slack is set to 400 nsec.
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
v5 -> v6
- switch to skb_set_delivery_time to also clear SKB_CLOCK_REALTIME
- add fq_offload_slack_ns
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 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.
---
net/sched/sch_fq.c | 22 ++++++++++++++++++----
1 file changed, 18 insertions(+), 4 deletions(-)
diff --git a/net/sched/sch_fq.c b/net/sched/sch_fq.c
index e0f2cd99e3b7..e7730fb04264 100644
--- a/net/sched/sch_fq.c
+++ b/net/sched/sch_fq.c@@ -159,6 +159,9 @@ struct fq_sched_data { u64 stat_allocation_errors; }; +/* EDT timestamps to clear beyond now. */ +static const int fq_offload_slack_ns = 400; + /* return the i-th 2-bit value ("crumb") */ static u8 fq_prio2band(const u8 *prio2band, unsigned int prio) {
@@ -399,6 +402,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 + fq_offload_slack_ns) + skb_set_delivery_time(skb, 0, 0); + return &q->internal; }
@@ -707,6 +715,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 +730,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 +767,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 +837,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 + fq_offload_slack_ns) + skb_set_delivery_time(skb, 0, 0); + return skb; }
--
2.55.0.691.gc56d675ccc-goog