From: netdev-bot+sashiko@kernel.org
To: willemdebruijn.kernel@gmail.com
Cc: netdev@vger.kernel.org, davem@davemloft.net, kuba@kernel.org,
edumazet@google.com, pabeni@redhat.com, horms@kernel.org,
andrew+netdev@lunn.ch, willemb@google.com
Subject: Re: [PATCH net-next v9 2/7] net_sched: sch_fq: check device pacing offload
Date: Fri, 11 Sep 2026 17:13:15 +0000 [thread overview]
Message-ID: <178914679525.219967.9347882764408033378@kernel.org> (raw)
In-Reply-To: <20260910171131.2532487-3-willemdebruijn.kernel@gmail.com>
Thank you for your contribution! Sashiko AI review found 4 potential
issue(s) to consider:
Critical: 0 · High: 0 · Medium: 2 · Low: 2
- [Medium] New, undocumented netlink rejection in fq_change(): `tc qdisc
{add,replace,change} ... fq offload_horizon N` with N != 0 now returns…
- [Medium] Configured vs. effective offload horizon are now inconsistent
across the three FQ interfaces.
- [Low] The patch introduces a per-packet TX fast-path read of
`dev->max_pacing_offload_horizon` (fq_offload_horizon(),…
- [Low] fq_offload_horizon() reads `dev->pacing_offload` as a plain,
unannotated load from the qdisc data path (net/sched/sch_fq.c:315,…
--
net_sched: sch_fq: check device pacing offload
FQ now consults the device's pacing offload state before applying the
configured offload horizon, via a new fq_offload_horizon() helper that is
evaluated once per packet in fq_classify() and fq_dequeue(). The helper
returns 0 when the device flag is clear or when the configured horizon
exceeds the device maximum, and fq_change() gains a new rejection when a
non-zero horizon is requested on a device with pacing offload disabled.
A few questions below.
> diff --git a/net/sched/sch_fq.c b/net/sched/sch_fq.c
> index 35f940b2205df..dfc7f98b600f8 100644
> --- a/net/sched/sch_fq.c
> +++ b/net/sched/sch_fq.c
> @@ -301,6 +301,26 @@ static void fq_gc(struct fq_sched_data *q,
> q->stat_gc_flows += fcnt;
> }
>
> +static u64 fq_offload_horizon(const struct Qdisc *sch,
> + const struct fq_sched_data *q)
> +{
> + const struct net_device *dev;
> + u64 offload_horizon;
> +
> + offload_horizon = READ_ONCE(q->offload_horizon);
> + if (!offload_horizon)
> + return 0;
> +
> + dev = qdisc_dev(sch);
> + if (!dev->pacing_offload)
> + return 0;
[Severity: Low]
Is this read of dev->pacing_offload racy?
The two neighbouring loads in this same helper use READ_ONCE(), but
pacing_offload is read plainly here. It is a 1-bit bitfield inside
struct_group(priv_flags_fast) in struct net_device, and the sibling
rtnetlink change makes it runtime-writable:
net/core/rtnetlink.c:do_setlink() {
if (tb[IFLA_PACING_OFFLOAD]) {
bool val = nla_get_u32(tb[IFLA_PACING_OFFLOAD]);
if (dev->pacing_offload != val) {
dev->pacing_offload = val;
...
}
The writer holds only RTNL, while this reader runs from fq_classify() and
fq_dequeue() under the qdisc root lock, so the two sides share no lock and
both accesses are plain. Would KCSAN flag this?
Because it is a bitfield, READ_ONCE()/WRITE_ONCE() cannot be applied to it.
Would a dedicated non-bitfield member (or bitops on a separate unsigned
long), or at minimum data_race() on this read, be preferable?
Separately, storing a single bit is a read-modify-write of the whole
declared unsigned long storage unit, which also holds priv_flags:32,
lltx:1 and netmem_tx:2 - fields read locklessly on the TX path. Which
lock is intended to serialize RMWs of that word now that one of its bits
changes at runtime? I did not find a non-RTNL writer of the neighbouring
bits, so this part is speculative.
> +
> + if (offload_horizon > READ_ONCE(dev->max_pacing_offload_horizon))
> + return 0;
> +
> + return offload_horizon;
> +}
> +
[Severity: Low]
Should Documentation/networking/net_cachelines/net_device.rst be updated
here?
This adds a per-packet TX read of dev->max_pacing_offload_horizon, but the
row for that field in net_device.rst still has empty fastpath_tx_access and
Comments columns. The companion bit added by the preceding patch in the
series was annotated:
unsigned_long:1 pacing_offload read_mostly ... sch_fq
struct net_device also carries a comment asking for the document to be kept
in sync ("Please update the document when adding new fields").
max_pacing_offload_horizon still lives in the cold region of struct
net_device next to irq_moder/napi_config, outside the net_device_read_tx
cacheline group, so for qdiscs that configured an offload horizon the TX
path now touches an extra otherwise-cold cache line per enqueue and per
dequeue. Was that placement considered?
> /* Fast path can be used if :
> * 1) Packet tstamp is in the past, or within the pacing offload horizon.
> * 2) FQ qlen == 0 OR
[ ... ]
> @@ -357,6 +377,7 @@ static struct fq_flow *fq_classify(struct Qdisc *sch, struct sk_buff *skb,
> u64 now)
> {
> struct fq_sched_data *q = qdisc_priv(sch);
> + u64 offload_horizon = fq_offload_horizon(sch, q);
> struct rb_node **p, *parent;
> struct sock *sk = skb->sk;
> struct rb_root *root;
[ ... ]
> @@ -705,6 +728,7 @@ static struct fq_flow_head *fq_pband_head_select(struct fq_perband_flows *pband)
> static struct sk_buff *fq_dequeue(struct Qdisc *sch)
> {
> struct fq_sched_data *q = qdisc_priv(sch);
> + u64 offload_horizon = fq_offload_horizon(sch, q);
> struct fq_perband_flows *pband;
> struct fq_flow_head *head;
> struct sk_buff *skb;
[ ... ]
> @@ -1179,11 +1204,15 @@ 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) {
> - WRITE_ONCE(q->offload_horizon, offload_horizon);
> - } else {
> + if (offload_horizon && !qdisc_dev(sch)->pacing_offload) {
> + NL_SET_ERR_MSG_MOD(extack, "device pacing offload is disabled");
> + err = -EINVAL;
[Severity: Medium]
Is this new configuration-time rejection intended, and should the commit
message mention it?
The changelog only describes runtime behaviour:
In FQ, only offload if the feature is enabled and within the supported
device horizon.
but this hunk also makes
tc qdisc replace dev X root fq offload_horizon 500us
return -EINVAL whenever qdisc_dev(sch)->pacing_offload is 0. Before the
patch the only gate was offload_horizon <= max_pacing_offload_horizon.
Since the only setter of that bit is do_setlink() via IFLA_PACING_OFFLOAD,
does this create an ordering dependency where the link attribute must be
enabled before the qdisc can be configured?
The new fq_offload_horizon() already degrades to 0 when the device flag is
clear, so the datapath is safe without the hard rejection:
dev = qdisc_dev(sch);
if (!dev->pacing_offload)
return 0;
Would accepting the value (and letting it stay inert until the device flag
is enabled) be preferable to failing the command?
> + } else if (offload_horizon >
> + qdisc_dev(sch)->max_pacing_offload_horizon) {
> NL_SET_ERR_MSG_MOD(extack, "invalid offload_horizon");
> err = -EINVAL;
> + } else {
> + WRITE_ONCE(q->offload_horizon, offload_horizon);
> }
> }
> if (!err) {
[Severity: Medium]
Does this make the value reported by fq_dump() inaccurate and no longer
re-appliable?
fq_dump() is untouched and still reports the raw stored value:
offload_horizon = READ_ONCE(q->offload_horizon);
do_div(offload_horizon, NSEC_PER_USEC);
...
nla_put_u32(skb, TCA_FQ_OFFLOAD_HORIZON, (u32)offload_horizon) ||
Consider:
ip link set dev X pacing_offload on
tc qdisc replace dev X root fq offload_horizon 500us /* stored */
ip link set dev X pacing_offload off /* do_setlink() */
Nothing clears q->offload_horizon, so fq_offload_horizon() returns 0 and
the datapath does pure software pacing while tc qdisc show still prints
offload_horizon 500us. Replaying that same dump then hits the new -EINVAL
above, where it previously succeeded.
The same silent divergence appears when a device lowers
max_pacing_offload_horizon on re-init, for example
idpf_vport_set_pacing_offload() doing
WRITE_ONCE(netdev->max_pacing_offload_horizon, max_horizon) with
max_horizon possibly 0 - the "device renegotiation" case named in the
changelog.
The uAPI comment also still promises unconditional behaviour:
include/uapi/linux/pkt_sched.h:
TCA_FQ_OFFLOAD_HORIZON, /* dequeue packets in the past, if within horizon */
Should fq_dump() report the effective horizon, or should the comment note
the dependency on device administrative state?
One smaller inconsistency: fq_offload_horizon() uses READ_ONCE() for
dev->max_pacing_offload_horizon while fq_change() reads the same field, and
pacing_offload, plainly.
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260910171131.2532487-1-willemdebruijn.kernel%40gmail.com
next prev parent reply other threads:[~2026-09-11 17:13 UTC|newest]
Thread overview: 20+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-10 17:10 [PATCH net-next v9 0/7] hardware pacing offload Willem de Bruijn
2026-09-10 17:10 ` [PATCH net-next v9 1/7] net: rtnetlink: add pacing_offload attribute to net_device Willem de Bruijn
2026-09-11 17:13 ` netdev-bot+sashiko
2026-09-12 0:25 ` Willem de Bruijn
2026-09-10 17:10 ` [PATCH net-next v9 2/7] net_sched: sch_fq: check device pacing offload Willem de Bruijn
2026-09-11 17:13 ` netdev-bot+sashiko [this message]
2026-09-12 0:33 ` Willem de Bruijn
2026-09-10 17:10 ` [PATCH net-next v9 3/7] net_sched: sch_fq: clear past skb->tstamp if offloading pacing Willem de Bruijn
2026-09-11 17:13 ` netdev-bot+sashiko
2026-09-12 0:36 ` Willem de Bruijn
2026-09-10 17:10 ` [PATCH net-next v9 4/7] idpf: support pacing offload Willem de Bruijn
2026-09-11 17:13 ` netdev-bot+sashiko
2026-09-12 0:46 ` Willem de Bruijn
2026-09-10 17:10 ` [PATCH net-next v9 5/7] selftests: drv-net: refactor so_txtime errqueue handling Willem de Bruijn
2026-09-10 17:10 ` [PATCH net-next v9 6/7] selftests: drv-net: in so_txtime tell apart sw from hw pacing Willem de Bruijn
2026-09-11 17:13 ` netdev-bot+sashiko
2026-09-12 0:47 ` Willem de Bruijn
2026-09-10 17:10 ` [PATCH net-next v9 7/7] selftests: drv-net: extend so_txtime with hw offload Willem de Bruijn
2026-09-11 17:13 ` netdev-bot+sashiko
2026-09-12 0:57 ` Willem de Bruijn
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=178914679525.219967.9347882764408033378@kernel.org \
--to=netdev-bot+sashiko@kernel.org \
--cc=andrew+netdev@lunn.ch \
--cc=davem@davemloft.net \
--cc=edumazet@google.com \
--cc=horms@kernel.org \
--cc=kuba@kernel.org \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=willemb@google.com \
--cc=willemdebruijn.kernel@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox