Netdev List
 help / color / mirror / Atom feed
* Re: [PATCH 2/2] net: thunderbolt: enlarge RX/TX ring and set NAPI weight for sustained load
From: Andrew Lunn @ 2026-04-28 14:39 UTC (permalink / raw)
  To: Mika Westerberg
  Cc: Benjamin Berman, Andreas Noever, Mika Westerberg, Yehezkel Bernat,
	Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, linux-usb, netdev, linux-kernel
In-Reply-To: <20260428141954.GT557136@black.igk.intel.com>

> > Is there interrupt coalesce going on, and the coalesce time set too
> > high, so that by the time the interrupt fires the ring is full? Can
> > you play with ethtool -C?
> 
> Thanks!
> 
> I'll leave these to Benjamin and Claude AI to answer.
> 
> One thing that could affect is the interrupt throttling that the hardware
> is doing. We have quite big value there by default. Lowering that may have
> affect as well. I just posted a patch series where one of the patches makes
> this configurable in the tbnet driver so you could apply that and play with
> the throttling value:
> 
> https://lore.kernel.org/linux-usb/20260428072209.3084930-6-mika.westerberg@linux.intel.com/

So i guess this is interrupt coalesce by another name.

In netdev, the module parameter would get NACKed.

[Looks more closely at the code].

So, you are adding the module parameter in
drivers/net/thunderbolt/main.c. That clearly is netdev code. So sorry,
please don't do that.

But ethtool provides an API for configuring these things:

       ethtool -C|--coalesce devname [adaptive-rx on|off] [adaptive-tx on|off]
              [rx-usecs N] [rx-frames N] [rx-usecs-irq N] [rx-frames-irq N]
              [tx-usecs N] [tx-frames N] [tx-usecs-irq N] [tx-frames-irq N]
              [stats-block-usecs N] [pkt-rate-low N] [rx-usecs-low N]
              [rx-frames-low N] [tx-usecs-low N] [tx-frames-low N]
              [pkt-rate-high N] [rx-usecs-high N] [rx-frames-high N]
              [tx-usecs-high N] [tx-frames-high N] [sample-interval N]

So i hope you can map these parameters to your tb_ring_throttling()
call.

   Andrew

^ permalink raw reply

* Re: [PATCH net-next 3/3] net/mlx5: use internal dma pools for frag buf alloc
From: Leon Romanovsky @ 2026-04-28 14:47 UTC (permalink / raw)
  To: Tariq Toukan
  Cc: Eric Dumazet, Jakub Kicinski, Paolo Abeni, Andrew Lunn,
	David S. Miller, Saeed Mahameed, Mark Bloch, netdev, linux-rdma,
	linux-kernel, Gal Pressman, Dragos Tatulea, Moshe Shemesh,
	Nimrod Oren
In-Reply-To: <20260428052920.219201-4-tariqt@nvidia.com>

On Tue, Apr 28, 2026 at 08:29:20AM +0300, Tariq Toukan wrote:
> From: Nimrod Oren <noren@nvidia.com>
> 
> Add mlx5_dma_pool alloc/free paths, and wire mlx5_frag_buf allocation
> and free paths to use them.
> 
> mlx5_frag_buf_alloc_node() now selects an mlx5_dma_pool to allocate
> fragments from, instead of directly allocating full coherent pages.
> 
> mlx5_frag_buf_free() frees from the respective pool.
> 
> mlx5_dma_pool_alloc() keeps allocation fast by maintaining pages with
> available indexes at the head of the list, so the common allocation path
> can take a free index immediately. New backing pages are allocated only
> when no free index is available.
> 
> mlx5_dma_pool_free() returns released indexes to the pool and frees a
> backing page once all of its indexes become free. This avoids keeping
> fully free pages for the lifetime of the pool and reduces coherent DMA
> memory footprint.
> 
> Signed-off-by: Nimrod Oren <noren@nvidia.com>
> Signed-off-by: Tariq Toukan <tariqt@nvidia.com>
> ---
>  .../net/ethernet/mellanox/mlx5/core/alloc.c   | 185 ++++++++++++++----
>  include/linux/mlx5/driver.h                   |   2 +
>  2 files changed, 154 insertions(+), 33 deletions(-)

<...>

> +	if (WARN_ONCE(idx >= blocks_per_page,
> +		      "mlx5 dma pool invalid idx: %lu (max %d)\n",
> +		      idx, blocks_per_page - 1))
> +		return;

<...>

> +	if (WARN_ONCE(test_bit(idx, page->bitmap),
> +		      "mlx5 dma pool double free: idx=%lu block_shift=%u\n",
> +		      idx, pool->block_shift))
> +		goto unlock;

<...>

> +	if (WARN_ONCE(size <= 0, "mlx5_frag_buf non-positive size: %d\n", size))
> +		return -EINVAL;

<...>

> +	if (WARN_ONCE(node < 0 || node >= nr_node_ids || !node_possible(node),
> +		      "mlx5_frag_buf invalid node ID: %d\n", node))
> +		return -EINVAL;

All WARN_ONCE() instances in this patch and the previous one are not
reachable. WARN_ONCE() should be used to detect states that are truly
impossible, not cases where the internal API is being misused.

There is no need for defensive programming when dealing with
in-kernel or in-driver APIs.

Thanks

^ permalink raw reply

* Re: [PATCH net-next v9 4/4] tun/tap & vhost-net: avoid ptr_ring tail-drop when a qdisc is present
From: Simon Schippers @ 2026-04-28 14:55 UTC (permalink / raw)
  To: Michael S. Tsirkin
  Cc: willemdebruijn.kernel, jasowang, andrew+netdev, davem, edumazet,
	kuba, pabeni, eperezma, leiyang, stephen, jon, tim.gebauer,
	netdev, linux-kernel, kvm, virtualization
In-Reply-To: <20260428103108-mutt-send-email-mst@kernel.org>

On 4/28/26 16:32, Michael S. Tsirkin wrote:
> On Tue, Apr 28, 2026 at 04:18:54PM +0200, Simon Schippers wrote:
>> On 4/28/26 16:10, Michael S. Tsirkin wrote:
>>> On Tue, Apr 28, 2026 at 03:41:20PM +0200, Simon Schippers wrote:
>>>> On 4/28/26 15:22, Michael S. Tsirkin wrote:
>>>>> On Tue, Apr 28, 2026 at 03:10:44PM +0200, Simon Schippers wrote:
>>>>>> On 4/28/26 14:50, Michael S. Tsirkin wrote:
>>>>>>> On Tue, Apr 28, 2026 at 02:38:59PM +0200, Simon Schippers wrote:
>>>>>>>> This commit prevents tail-drop when a qdisc is present and the ptr_ring
>>>>>>>> becomes full. Once an entry is successfully produced and the ptr_ring
>>>>>>>> reaches capacity, the netdev queue is stopped instead of dropping
>>>>>>>> subsequent packets.
>>>>>>>>
>>>>>>>> If producing an entry fails anyways due to a race, tun_net_xmit returns
>>>>>>>> NETDEV_TX_BUSY, again avoiding a drop. Such races are expected because
>>>>>>>> LLTX is enabled and the transmit path operates without the usual locking.
>>>>>>>>
>>>>>>>> If no qdisc is present, the previous tail-drop behavior is preserved.
>>>>>>>>
>>>>>>>> The existing __tun_wake_queue() function of the consumer races with the
>>>>>>>> producer for waking/stopping the netdev queue: the consumer may drain
>>>>>>>> the ring just as the producer stops the queue, leading to a permanent
>>>>>>>> stall. To avoid this, the producer re-checks the ring after stopping
>>>>>>>> and wakes the queue itself if space was just made. An
>>>>>>>> smp_mb__after_atomic() is required so the re-peek of the ring sees any
>>>>>>>> drain that the consumer performed.
>>>>>>>> smp_mb__after_atomic() pairs with the test_and_clear_bit() inside of
>>>>>>>> netif_wake_subqueue():
>>>>>>>>
>>>>>>>> Consumer CPU                  Producer CPU
>>>>>>>> ========================      =========================
>>>>>>>> __ptr_ring_consume()
>>>>>>>> netif_wake_subqueue()         netif_tx_stop_queue()
>>>>>>>>           /\                  smp_mb__after_atomic()
>>>>>>>>           ||                  __ptr_ring_produce_peek()
>>>>>>>> contains RMW operation
>>>>>>>>  test_and_clear_bit()
>>>>>>>>           /\
>>>>>>>>           ||
>>>>>>>>  "Fully ordered RMW:
>>>>>>>> smp_mb() before + after"
>>>>>>>>     - atomic_t.txt
>>>>>>>>
>>>>>>>> Benchmarks:
>>>>>>>> The benchmarks show a slight regression in raw transmission performance,
>>>>>>>> though no packets are lost anymore.
>>>>>>>
>>>>>>> Could you include the packets received as well?
>>>>>>> To demonstrate the gains/lack of loss. 
>>>>>>>
>>>>>>
>>>>>> Do you mean the number of packets received by the VM?
>>>>>> They should just be the same as the number sent (shown below), right?
>>>>>
>>>>> Minus the loss? Which this is about, right?
>>>>
>>>> Yes. I simply calculated "Lost/s":
>>>>
>>>> elapsed_time = 100e6 / sent_pps
>>>> Lost/s = total_errors / elapsed_time
>>>>
>>>>
>>>> To get back total_errors for example for TAP
>>>> 1 thread sending:
>>>>
>>>> elapsed_time = 100e6 / 1.136Mpps = 88s
>>>>
>>>> 3758 Mpps = total_errors / 88s
>>>> <=> total_errors = 331 million packets
>>>>
>>>> So, out of 431 million packets sent, 100 million were successfully
>>>> delivered and 331 million were lost.
>>>
>>> That is my issue.
>>>
>>> I kind of have trouble mapping that to the table below.
>>> For example:
>>>
>>>  | TAP        | Transmitted | 1.136 Mpps   | 1.130 Mpps     | -0.6%    |
>>>  |            +-------------+--------------+----------------+----------+
>>>  |            | Lost/s      | 3.758 Mpps   | 0 pps          |          |
>>>
>>> how can # of lost packets exceed the # of transmitted packets?
>>>
>>> Thanks!
>>
>> I just do use the sample script [1]:
>>
>> ./pktgen_sample02_multiqueue.sh -n 100000000 ...
>>
>> ... and this runs until 100_000_000 packets were sucessfully
>> transmitted, independently of the lost packets/errors.
>>
>> [1] Link: https://www.kernel.org/doc/html/latest/networking/pktgen.html#sample-scripts
> 
> Confused. Are you saying "transmitted" is actually "received"? And the #
> of packets sent is Transmitted + Lost?

Sorry for my confusing answer.

Yes, "transmitted" in the table should be changed to "received".
And yes, as you said, the real # transmitted then is:
Received + Lost = 1.136 + 3.758 = 4.894 Mpps.

> 
>>>
>>>
>>>>>
>>>>>> I assume they would be visible as RX-DRP for TAP.
>>>>>> For TAP + vhost-net I would have to rewrite the XDP drop
>>>>>> program to count the number of dropped packets...
>>>>>> And I would have to automate it...
>>>>>>
>>>>>>>>
>>>>>>>> The previously introduced threshold to only wake after the queue stopped
>>>>>>>> and half of the ring was consumed showed to be a descent choice:
>>>>>>>> Waking the queue whenever a consume made space in the ring strongly
>>>>>>>> degrades performance for tap, while waking only when the ring is empty
>>>>>>>> is too late and also hurts throughput for tap & tap+vhost-net.
>>>>>>>> Other ratios (3/4, 7/8) showed similar results (not shown here), so
>>>>>>>> 1/2 was chosen for the sake of simplicity for both tun/tap and
>>>>>>>> tun/tap+vhost-net.
>>>>>>>>
>>>>>>>> Test setup:
>>>>>>>> AMD Ryzen 5 5600X at 4.3 GHz, 3200 MHz RAM, isolated QEMU threads;
>>>>>>>> Average over 50 runs @ 100,000,000 packets. SRSO and spectre v2
>>>>>>>> mitigations disabled.
>>>>>>>>
>>>>>>>> Note for tap+vhost-net:
>>>>>>>> XDP drop program active in VM -> ~2.5x faster, slower for tap due to
>>>>>>>> more syscalls (high utilization of entry_SYSRETQ_unsafe_stack in perf)
>>>>>>>>
>>>>>>>> +--------------------------+--------------+----------------+----------+
>>>>>>>> | 1 thread                 | Stock        | Patched with   | diff     |
>>>>>>>> | sending                  |              | fq_codel qdisc |          |
>>>>>>>> +------------+-------------+--------------+----------------+----------+
>>>>>>>> | TAP        | Transmitted | 1.136 Mpps   | 1.130 Mpps     | -0.6%    |
>>>>>>>> |            +-------------+--------------+----------------+----------+
>>>>>>>> |            | Lost/s      | 3.758 Mpps   | 0 pps          |          |
>>>>>>>> +------------+-------------+--------------+----------------+----------+
>>>>>>>> | TAP        | Transmitted | 3.858 Mpps   | 3.816 Mpps     | -1.1%    |
>>>>>>>> |            +-------------+--------------+----------------+----------+
>>>>>>>> | +vhost-net | Lost/s      | 789.8 Kpps   | 0 pps          |          |
>>>>>>>> +------------+-------------+--------------+----------------+----------+
>>>>>>>>
>>>>>>>> +--------------------------+--------------+----------------+----------+
>>>>>>>> | 2 threads                | Stock        | Patched with   | diff     |
>>>>>>>> | sending                  |              | fq_codel qdisc |          |
>>>>>>>> +------------+-------------+--------------+----------------+----------+
>>>>>>>> | TAP        | Transmitted | 1.117 Mpps   | 1.087 Mpps     | -2.7%    |
>>>>>>>> |            +-------------+--------------+----------------+----------+
>>>>>>>> |            | Lost/s      | 8.476 Mpps   | 0 pps          |          |
>>>>>>>> +------------+-------------+--------------+----------------+----------+
>>>>>>>> | TAP        | Transmitted | 3.679 Mpps   | 3.464 Mpps     | -5.8%    |
>>>>>>>> |            +-------------+--------------+----------------+----------+
>>>>>>>> | +vhost-net | Lost/s      | 5.306 Mpps   | 0 pps          |          |
>>>>>>>> +------------+-------------+--------------+----------------+----------+
>>>>>>>>
>>>>>>>> Co-developed-by: Tim Gebauer <tim.gebauer@tu-dortmund.de>
>>>>>>>> Signed-off-by: Tim Gebauer <tim.gebauer@tu-dortmund.de>
>>>>>>>> Signed-off-by: Simon Schippers <simon.schippers@tu-dortmund.de>
>>>>>>>> ---
>>>>>>>>  drivers/net/tun.c | 30 ++++++++++++++++++++++++++++--
>>>>>>>>  1 file changed, 28 insertions(+), 2 deletions(-)
>>>>>>>>
>>>>>>>> diff --git a/drivers/net/tun.c b/drivers/net/tun.c
>>>>>>>> index efe809597622..c2a1618cc9db 100644
>>>>>>>> --- a/drivers/net/tun.c
>>>>>>>> +++ b/drivers/net/tun.c
>>>>>>>> @@ -1011,6 +1011,8 @@ static netdev_tx_t tun_net_xmit(struct sk_buff *skb, struct net_device *dev)
>>>>>>>>  	struct netdev_queue *queue;
>>>>>>>>  	struct tun_file *tfile;
>>>>>>>>  	int len = skb->len;
>>>>>>>> +	bool qdisc_present;
>>>>>>>> +	int ret;
>>>>>>>>  
>>>>>>>>  	rcu_read_lock();
>>>>>>>>  	tfile = rcu_dereference(tun->tfiles[txq]);
>>>>>>>> @@ -1065,13 +1067,37 @@ static netdev_tx_t tun_net_xmit(struct sk_buff *skb, struct net_device *dev)
>>>>>>>>  
>>>>>>>>  	nf_reset_ct(skb);
>>>>>>>>  
>>>>>>>> -	if (ptr_ring_produce(&tfile->tx_ring, skb)) {
>>>>>>>> +	queue = netdev_get_tx_queue(dev, txq);
>>>>>>>> +	qdisc_present = !qdisc_txq_has_no_queue(queue);
>>>>>>>> +
>>>>>>>> +	spin_lock(&tfile->tx_ring.producer_lock);
>>>>>>>> +	ret = __ptr_ring_produce(&tfile->tx_ring, skb);
>>>>>>>> +	if (__ptr_ring_produce_peek(&tfile->tx_ring) && qdisc_present) {
>>>>>>>> +		netif_tx_stop_queue(queue);
>>>>>>>> +		/* Re-peek and wake if the consumer drained the ring
>>>>>>>> +		 * concurrently in a race. smp_mb__after_atomic() pairs
>>>>>>>> +		 * with the test_and_clear_bit() of netif_wake_subqueue()
>>>>>>>> +		 * in __tun_wake_queue().
>>>>>>>> +		 */
>>>>>>>> +		smp_mb__after_atomic();
>>>>>>>> +		if (!__ptr_ring_produce_peek(&tfile->tx_ring))
>>>>>>>> +			netif_tx_wake_queue(queue);
>>>>>>>> +	}
>>>>>>>> +	spin_unlock(&tfile->tx_ring.producer_lock);
>>>>>>>> +
>>>>>>>> +	if (ret) {
>>>>>>>> +		/* If a qdisc is attached to our virtual device,
>>>>>>>> +		 * returning NETDEV_TX_BUSY is allowed.
>>>>>>>> +		 */
>>>>>>>> +		if (qdisc_present) {
>>>>>>>> +			rcu_read_unlock();
>>>>>>>> +			return NETDEV_TX_BUSY;
>>>>>>>> +		}
>>>>>>>>  		drop_reason = SKB_DROP_REASON_FULL_RING;
>>>>>>>>  		goto drop;
>>>>>>>>  	}
>>>>>>>>  
>>>>>>>>  	/* dev->lltx requires to do our own update of trans_start */
>>>>>>>> -	queue = netdev_get_tx_queue(dev, txq);
>>>>>>>>  	txq_trans_cond_update(queue);
>>>>>>>>  
>>>>>>>>  	/* Notify and wake up reader process */
>>>>>>>> -- 
>>>>>>>> 2.43.0
>>>>>>>
>>>>>
>>>
> 

^ permalink raw reply

* [PATCH] net/sched: sch_dualpi2: Add missing skb check
From: Manas @ 2026-04-28 14:57 UTC (permalink / raw)
  To: jhs, jiri, davem, edumazet, kuba, pabeni, horms, rakshitawasthi17
  Cc: netdev, linux-kernel, Manas

A packed is dequeued using the function `dequeue_packet()` but there is
no check to see if a packet is dequeued. The assignment is always
considered as true resulting in null-ptr-deref

Fixes: 8f9516daedd6 ("sched: Add enqueue/dequeue of dualpi2 qdisc")
Reported-by: Manas <ghandatmanas@gmail.com>
Reported-by: Rakshit Awasthi <rakshitawasthi17@gmail.com>
Signed-off-by: Manas <ghandatmanas@gmail.com>
---
 net/sched/sch_dualpi2.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/net/sched/sch_dualpi2.c b/net/sched/sch_dualpi2.c
index 241e6a46bd00..e360bf16d617 100644
--- a/net/sched/sch_dualpi2.c
+++ b/net/sched/sch_dualpi2.c
@@ -588,7 +588,7 @@ static struct sk_buff *dualpi2_qdisc_dequeue(struct Qdisc *sch)
 
 	now = ktime_get_ns();
 
-	while ((skb = dequeue_packet(sch, q, &credit_change, now))) {
+	while ((skb = dequeue_packet(sch, q, &credit_change, now)) && skb) {
 		if (!q->drop_early && must_drop(sch, q, skb)) {
 			drop_and_retry(q, skb, sch, QDISC_DROP_CONGESTED);
 			continue;
-- 
2.43.0


^ permalink raw reply related

* Re: [PATCH v3 net-next] net/intel: Replace manual array size calculation with ARRAY_SIZE
From: Dan Carpenter @ 2026-04-28 14:59 UTC (permalink / raw)
  To: Przemek Kitszel
  Cc: Jakub Raczynski, netdev, kuba, intel-wired-lan, linux-kernel,
	kernel-janitors
In-Reply-To: <ef61b770-ebf4-406a-90a0-8a49aba02aee@intel.com>

On Tue, Apr 28, 2026 at 04:06:48PM +0200, Przemek Kitszel wrote:
> On 4/28/26 12:36, Jakub Raczynski wrote:
> > There are still places in the code where manual calculation of array size
> > exist, but it is good to enforce usage of single macro through the whole
> > code as it makes code bit more readable.
> > While at it, beautify condition surrounding it by reversing check and remove
> > unnecessary casting.
> > 
> > Signed-off-by: Jakub Raczynski <j.raczynski@samsung.com>
> > Reviewed-by: Dan Carpenter <error27@gmail.com>
> 
> thank you,
> Reviewed-by: Przemek Kitszel <przemyslaw.kitszel@intel.com>
> 
> (next time use "iwl-next" in the title, no need to repost just for that)
> 

These prefixes are a headache.  It seems like no big deal if you only
work on one subsystem, but if you're dealing with tree wide code then
we're dealing with 418 trees.

These days I have a script for net and net-next because they are a
high volume list and the original prefix subsystem.  But otherwise
it just automatically puts [PATCH next] if it's in linux-next or
[PATCH] if it's in Linus's tree.

These are all automatic on my end, right?  So it would be totally
possible to automate in the recieving side instead.

Although in this case, my script would net-next in the subject because
I had no idea that Intel was managing their own ethernet drivers...  It
used to be that wireless had their own tree and everything else went
through net.  So it's just seems like a in impossible task to keep track
of it all even if you are not a newbie.  :/

regards,
dan carpenter


^ permalink raw reply

* Re: [PATCH 5/9] thunderbolt / net: Let the service drivers configure interrupt throttling
From: Andrew Lunn @ 2026-04-28 14:59 UTC (permalink / raw)
  To: Mika Westerberg
  Cc: linux-usb, Yehezkel Bernat, Lukas Wunner, Andreas Noever,
	Alan Borzeszkowski, Andrew Lunn, David S . Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, netdev
In-Reply-To: <20260428072209.3084930-6-mika.westerberg@linux.intel.com>

On Tue, Apr 28, 2026 at 09:22:05AM +0200, Mika Westerberg wrote:
> Instead of the core driver programming fixed value for throttling let
> the service drivers to specify the interval if they need this. We also
> allow user to tune this through a module parameter if the default is not
> good fit.
> 
> Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
> ---
>  drivers/net/thunderbolt/main.c |  7 ++++
>  drivers/thunderbolt/dma_test.c |  5 +++
>  drivers/thunderbolt/nhi.c      | 58 ++++++++++++++++++----------------
>  drivers/thunderbolt/nhi_regs.h |  3 +-
>  include/linux/thunderbolt.h    |  5 +++
>  5 files changed, 50 insertions(+), 28 deletions(-)
> 
> diff --git a/drivers/net/thunderbolt/main.c b/drivers/net/thunderbolt/main.c
> index 49673f7e0055..8771ca807933 100644
> --- a/drivers/net/thunderbolt/main.c
> +++ b/drivers/net/thunderbolt/main.c
> @@ -218,6 +218,10 @@ static bool tbnet_e2e = true;
>  module_param_named(e2e, tbnet_e2e, bool, 0444);
>  MODULE_PARM_DESC(e2e, "USB4NET full end-to-end flow control (default: true)");
>  
> +static unsigned int tbnet_throttling = 128000;
> +module_param_named(throttling, tbnet_throttling, uint, 0444);
> +MODULE_PARM_DESC(throttling, "Interrupt throttling rate in ns (default: 128000)");

As i mentioned elsewhere, netdev does not allow module
parameters. They are hard to use, especially when you have lots of
instances of a device, or you need to set it on the kernel command
line because by the time the kernel has booted, it is too late, etc.
And they are undocumented, and every driver does it differently.

The correct way to do this for netdev it ethtool -C.

For something which is not a netdev, like your stream file, you have
more flexibility, but the same usability issues apply.

	Andrew

^ permalink raw reply

* Re: [PATCH v3 net-next] net/intel: Replace manual array size calculation with ARRAY_SIZE
From: Dan Carpenter @ 2026-04-28 15:01 UTC (permalink / raw)
  To: Przemek Kitszel
  Cc: Jakub Raczynski, netdev, kuba, intel-wired-lan, linux-kernel,
	kernel-janitors
In-Reply-To: <ef61b770-ebf4-406a-90a0-8a49aba02aee@intel.com>

On Tue, Apr 28, 2026 at 04:06:48PM +0200, Przemek Kitszel wrote:
> On 4/28/26 12:36, Jakub Raczynski wrote:
> > There are still places in the code where manual calculation of array size
> > exist, but it is good to enforce usage of single macro through the whole
> > code as it makes code bit more readable.
> > While at it, beautify condition surrounding it by reversing check and remove
> > unnecessary casting.
> > 
> > Signed-off-by: Jakub Raczynski <j.raczynski@samsung.com>
> > Reviewed-by: Dan Carpenter <error27@gmail.com>
> 
> thank you,
> Reviewed-by: Przemek Kitszel <przemyslaw.kitszel@intel.com>
> 
> (next time use "iwl-next" in the title, no need to repost just for that)
> 

Which sub directories go through iwl-next?  I was trying to update
my script

> > 
> >   drivers/net/ethernet/intel/i40e/i40e_adminq.h | 2 +-
> >   drivers/net/ethernet/intel/iavf/iavf_adminq.h | 2 +-
> >   2 files changed, 2 insertions(+), 2 deletions(-)

but look at these file names. There is no "iwl" anywhere in
the names!  :(

regards,
dan carpenter


^ permalink raw reply

* Re: [PATCH net 11/11] ipvs: fix the spin_lock usage for RT build
From: Julian Anastasov @ 2026-04-28 15:04 UTC (permalink / raw)
  To: Jakub Kicinski
  Cc: pablo, netfilter-devel, davem, netdev, pabeni, edumazet, fw,
	horms
In-Reply-To: <20260428021311.793367-1-kuba@kernel.org>


	Hello,

On Mon, 27 Apr 2026, Jakub Kicinski wrote:

> This is an AI-generated review of your patch. The human sending this
> email has considered the AI review valid, or at least plausible.
> ---
> ipvs: fix the spin_lock usage for RT build
> 
> This patch fixes PREEMPT_RT issues with spin_lock usage under bit
> locks in the IPVS resizable hash tables, and reworks ip_vs_conn_hash(),
> ip_vs_conn_unlink(), and ip_vs_conn_fill_cport() accordingly.
> 
> Link: https://sashiko.dev/#/patchset/20260415200216.79699-1-ja%40ssi.bg
> Link: https://sashiko.dev/#/patchset/20260420165539.85174-4-ja%40ssi.bg
> Link: https://sashiko.dev/#/patchset/20260422135823.50489-4-ja%40ssi.bg
> 
> > diff --git a/net/netfilter/ipvs/ip_vs_conn.c b/net/netfilter/ipvs/ip_vs_conn.c
> > index 84a4921a7865..9e23cda84825 100644
> > --- a/net/netfilter/ipvs/ip_vs_conn.c
> > +++ b/net/netfilter/ipvs/ip_vs_conn.c
> 
> [ ... ]
> 
> > @@ -637,6 +628,7 @@ void ip_vs_conn_fill_cport(struct ip_vs_conn *cp, __be16 cport)
> >  	struct ip_vs_conn_hnode *hn;
> >  	u32 hash_key, hash_key_new;
> >  	struct ip_vs_conn_param p;
> > +	bool by_me = false;
> >  	int ntbl;
> >  	int dir;
> >  
> > @@ -664,8 +656,11 @@ void ip_vs_conn_fill_cport(struct ip_vs_conn *cp, __be16 cport)
> >  		t = rcu_dereference(t->new_tbl);
> >  		ntbl++;
> >  		/* We are lost? */
> > -		if (ntbl >= 2)
> > +		if (ntbl >= 2) {
> > +			IP_VS_ERR_RL("%s(): Too many ht changes for dir %d\n",
> > +				     __func__, dir);
> >  			return;
> > +		}
> >  	}
> 
> Can the ntbl >= 2 early return strand cp in an unrecoverable state
> when it is reached on the dir=0 iteration after dir=1 has already
> committed partial state?

	The right word here is "unreachable" but still recovarable
on expiration.

> In the pre-patch code, cp->cport, the clearing of
> IP_VS_CONN_F_NO_CPORT, and the atomic_dec() of no_cport_conns[] were
> all performed together inside a single if (!dir) critical section, so
> an abort via ntbl >= 2 simply left cp->cport == 0 and a later packet
> could retry from scratch.

	We saw this concern in previous review, and now it
reappeared. We explained in the commit that this ntbl >= 2 is a
situation that should not occur and added error to catch it. It is
a code that avoids dead loop. For connection with double hashing,
filling cport is a two-step operation, we fill cport early in
dir 1 (dir 0 follows in second step) to allow the resizing work (if 
running concurrently) to see valid hash key (cp->cport) all the time.
It is not fatal if we attempt second rehashing after this resizing,
ip_vs_conn_fill_cport() detects this with a 'if (head != head2)'
check, i.e. we are already on the desired bucket. Here is what
happens on resizing running concurrently with filling cport:

conn_resize_work_handler                ip_vs_conn_fill_cport
-------------------------------------------------------------
Still keep old ipvs->conn_tab but
rcu_assign_pointer(t->new_tbl, t_new)
to attach new table to the current
one

                                        Any RCU read-side sections here
                                        complete before the resizing.
                                        They may see the new table and
                                        move the nodes there. ntbl will
                                        not change above 0.
                                        
first synchronize_rcu() call

                                        rcu_read_lock() - after the
                                        synchronize_rcu() in resize_work

                                        - we see the new table in t->new_tbl
start to move conn nodes to             - we first process dir=1 concurrently
t->new_tbl:                             with resizing, successfully
- change hn->hash_key under             
locks, now hn->hash_key points to       We now process dir=0 (hn0):
t->new_tbl and it is not changed        We may see the old hn->hash_key or
until new resize/fill_cport             the new one. The old matches the
                                        current table. When we reach the locks
                                        we will see the new value if it 
                                        is already assigned.

                                        When we see the early change of
                                        hn->hash_key which matches
                                        t->new_tbl (not t) => ntbl++ (ntbl
                                        becomes 1) and t jumps to t->new_tbl,
                                        i.e. we see that entry is linked
                                        already on the new table.

                                        In all cases we should see ntbl
                                        to reach 1, not more. Because we
                                        do the resizing in steps which
                                        avoid one RCU section to see many
                                        new tables.
                                        If ntbl becomes 2 we have some 
                                        kind of corruption,
                                        so we just exit the loop.

Finally, install t_new as
ipvs->conn_tab
                                        Alternatively, for dir=0 we can see
                                        the new ipvs->conn_tab, so here
                                        hn->hash_key will match both t and
                                        t->new_tbl (they are same) and
                                        dir=0 will never goto retry,
                                        ntbl remains 0.

> 
> [ ... ]
> 
> > -	spin_lock(&cp->lock);
> > -	if ((cp->flags & IP_VS_CONN_F_NO_CPORT) &&
> > -	    (cp->flags & IP_VS_CONN_F_HASHED)) {
> > +	/* Fill cport once, even if multiple packets try to do it */
> > +	if (cp->flags & IP_VS_CONN_F_NO_CPORT && (!cp->cport || by_me)) {
> > +		/* If we race with resizing make sure cport is set for dir 1 */
> > +		if (!cp->cport) {
> > +			cp->cport = cport;
> > +			by_me = true;
> > +		}
> > +		if (!dir) {
> > +			atomic_dec(&ipvs->no_cport_conns[af_id]);
> > +			cp->flags &= ~IP_VS_CONN_F_NO_CPORT;
> > +		}
> 
> With the new structure, the dir=1 iteration (which runs first when
> use2 is true) sets cp->cport = cport and by_me = true, but leaves
> IP_VS_CONN_F_NO_CPORT set and does not touch no_cport_conns[].  The
> flag clear and the atomic_dec happen only in the subsequent dir=0
> iteration.
> 
> If the dir=0 iteration then returns early via the ntbl >= 2 path,
> either directly on entry or after an accumulated ntbl across retry:
> restarts (ntbl is reset per next_dir, not per retry), cp ends up
> with cp->cport set to the new value but IP_VS_CONN_F_NO_CPORT still
> set.
> 
> Can any subsequent ip_vs_conn_fill_cport() call complete the
> transition?  The gate is:
> 
> 	if (cp->flags & IP_VS_CONN_F_NO_CPORT && (!cp->cport || by_me)) {
> 
> For a fresh caller, by_me is false and cp->cport is now non-zero, so
> the gate is structurally unsatisfiable and the flag clear /
> atomic_dec never runs.
> 
> In __ip_vs_conn_in_get() the lookup
> 
> 	!p->cport ^ !(cp->flags & IP_VS_CONN_F_NO_CPORT)
> 
> evaluates false for this cp (flag still set, p->cport non-zero), and
> the cport=0 fallback also fails since p->cport != cp->cport (cp->cport
> is now non-zero).  Does this leave cp unreachable via forward
> direction lookup until the timer expires?

	Yes, we suspect corruption. If it is broken data in
the connection we isolate it (not reachable via lookup) and
then let it expire and later restore the no_cport_conns
counter. If we try to restore the cp->cport to 0 on ntbl >= 2
then we should go and rehash hn1 (dir=1) but it is not fatal
if we do not rehash it because next packet will come again in 
ip_vs_conn_fill_cport() and rehash it properly. This can be
done if we want retransmitted packet to continue with the
connection instead of expiring it:

diff --git a/net/netfilter/ipvs/ip_vs_conn.c b/net/netfilter/ipvs/ip_vs_conn.c
index 9e23cda84825..9ea6b4fa78bf 100644
--- a/net/netfilter/ipvs/ip_vs_conn.c
+++ b/net/netfilter/ipvs/ip_vs_conn.c
@@ -657,6 +657,11 @@ void ip_vs_conn_fill_cport(struct ip_vs_conn *cp, __be16 cport)
 		ntbl++;
 		/* We are lost? */
 		if (ntbl >= 2) {
+			spin_lock_bh(&cp->lock);
+			if (cp->flags & IP_VS_CONN_F_NO_CPORT && by_me)
+				cp->cport = 0;
+			/* hn1 will be rehashed on next packet */
+			spin_unlock_bh(&cp->lock);
 			IP_VS_ERR_RL("%s(): Too many ht changes for dir %d\n",
 				     __func__, dir);
 			return;

	I.e. this is a change we can do but in all cases we
are not sure why ntbl reaches 2.

> As a side effect, does no_cport_conns[af_id] stay elevated for the
> remainder of cp's lifetime, forcing ip_vs_conn_in_get() into the
> slower cport=0 fallback path for that af/netns while the stuck entry
> lives?

	For FTP service we are often using double lookup
when PASV connections are in negotiation phase (each up
to 120 secs).

Regards

--
Julian Anastasov <ja@ssi.bg>


^ permalink raw reply related

* Re: [Intel-wired-lan] [PATCH iwl-next v4 1/3] igc: remove unused autoneg_failed field
From: Paul Menzel @ 2026-04-28 15:06 UTC (permalink / raw)
  To: Faizal Abdul Rahim
  Cc: khai.wen.tan, anthony.l.nguyen, andrew+netdev, davem, edumazet,
	kuba, pabeni, intel-wired-lan, netdev, linux-kernel,
	faizal.abdul.rahim, hong.aun.looi, khai.wen.tan,
	Aleksandr Loktionov
In-Reply-To: <84b4f8bb-3c8c-4098-bc7f-7e9fd248c5ca@linux.intel.com>

Dear Faizal,


Am 28.04.26 um 12:39 schrieb Abdul Rahim, Faizal:

> On 28/4/2026 2:56 pm, Paul Menzel wrote:

>> Am 28.04.26 um 08:00 schrieb KhaiWenTan:
>>
>> (Should spaces be added in your name?)
>>
>>> From: Faizal Rahim <faizal.abdul.rahim@linux.intel.com>
>>>
>>> autoneg_failed in struct igc_mac_info is never set in the igc driver.
>>> Remove the field and the dead code checking it in
>>> igc_config_fc_after_link_up().
>>
>> Could you please elaborate. Why is removal the correct fix, and it’s 
>> not an incomplete feature? Does auto-negotiation always succeed?
> 
> Auto-negotiation does not always succeed, but igc does not use
> autoneg_failed to handle that case, the field was never set anywhere
> in the igc driver.
> 
> Before this patch, the only igc references to autoneg_failed were
> the struct member declaration and the read in
> igc_config_fc_after_link_up(). No igc code ever assigned it to true,
> and git history shows no commit that added a setter since the code
> creation in 2018.
> 
> The field originates from the e1000/e1000e fiber/serdes forced-link
> path: when MAC-level auto-negotiation on fiber times out, the driver
> forces link up and sets autoneg_failed so the flow-control code knows
> pause was not negotiated and must be forced. igc has no fiber or
> serdes media, it only supports copper (igc_media_type_copper), so
> the code that sets autoneg_failed was never ported.
> 
> On copper, PHY auto-negotiation failure is handled differently:
> - No link at all: igc_check_for_copper_link() returns before reaching
>    flow-control configuration, there's nothing to configure FC on.
> - Link present but autoneg not yet complete:
>    igc_config_fc_after_link_up() checks MII_SR_AUTONEG_COMPLETE and
>    returns early without resolving pause. The next link-status event
>    re-triggers the check.
> - Autoneg completes (including via parallel detection fallback when
>    the link partner doesn't autoneg): the PHY still sets
>    AUTONEG_COMPLETE but LP_ABILITY won't have PAUSE bits since the
>    partner never sent autoneg pages. The existing flow-control logic
>    in igc_config_fc_after_link_up() handles that correctly, it falls
>    through to igc_fc_none or igc_fc_rx_pause based on requested_mode.
> 
> None of these paths need autoneg_failed. Keeping the field would be 
> misleading to reader.

Thank you. For me the information about just supporting copper would be 
great to have in the commit message.

>>> Reviewed-by: Looi, Hong Aun <hong.aun.looi@intel.com>
>>
>> Please order it to not use the comma: Hong Aun Looi
> 
> Will do, thanks.
> 
>>> Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
>>> Signed-off-by: Faizal Rahim <faizal.abdul.rahim@linux.intel.com>
>>> Signed-off-by: KhaiWenTan <khai.wen.tan@linux.intel.com>
>>> ---
>>>   drivers/net/ethernet/intel/igc/igc_hw.h  |  1 -
>>>   drivers/net/ethernet/intel/igc/igc_mac.c | 16 +---------------
>>>   2 files changed, 1 insertion(+), 16 deletions(-)
>>>
>>> diff --git a/drivers/net/ethernet/intel/igc/igc_hw.h b/drivers/net/ 
>>> ethernet/intel/igc/igc_hw.h
>>> index be8a49a86d09..86ab8f566f44 100644
>>> --- a/drivers/net/ethernet/intel/igc/igc_hw.h
>>> +++ b/drivers/net/ethernet/intel/igc/igc_hw.h
>>> @@ -92,7 +92,6 @@ struct igc_mac_info {
>>>       bool asf_firmware_present;
>>>       bool arc_subsystem_valid;
>>>
>>> -    bool autoneg_failed;
>>>       bool get_link_status;
>>>   };
>>>
>>> diff --git a/drivers/net/ethernet/intel/igc/igc_mac.c b/drivers/net/ 
>>> ethernet/intel/igc/igc_mac.c
>>> index 7ac6637f8db7..142beb9ae557 100644
>>> --- a/drivers/net/ethernet/intel/igc/igc_mac.c
>>> +++ b/drivers/net/ethernet/intel/igc/igc_mac.c
>>> @@ -438,28 +438,14 @@ void igc_config_collision_dist(struct igc_hw *hw)
>>>    * Checks the status of auto-negotiation after link up to ensure 
>>> that the

Just for your information, that your mailer wraps the lines of the quotes.

[…]

>>>    * speed and duplex were not forced.  If the link needed to be 
>>> forced, then
>>>    * flow control needs to be forced also.  If auto-negotiation is 
>>> enabled
>>> - * and did not fail, then we configure flow control based on our link
>>> - * partner.
>>> + * then we configure flow control based on our link partner.
>>>    */
>>>   s32 igc_config_fc_after_link_up(struct igc_hw *hw)
>>>   {
>>>       u16 mii_status_reg, mii_nway_adv_reg, mii_nway_lp_ability_reg;
>>> -    struct igc_mac_info *mac = &hw->mac;
>>>       u16 speed, duplex;
>>>       s32 ret_val = 0;
>>>
>>> -    /* Check for the case where we have fiber media and auto-neg failed
>>> -     * so we had to force link.  In this case, we need to force the
>>> -     * configuration of the MAC to match the "fc" parameter.
>>> -     */
>>> -    if (mac->autoneg_failed)
>>> -        ret_val = igc_force_mac_fc(hw);
>>> -
>>> -    if (ret_val) {
>>> -        hw_dbg("Error forcing flow control settings\n");
>>> -        goto out;
>>> -    }
>>> -
>>>       /* In auto-neg, we need to check and see if Auto-Neg has 
>>> completed,
>>>        * and if so, how the PHY and link partner has flow control
>>>        * configured.

Kind regards,

Paul

^ permalink raw reply

* Re: [PATCH 9/9] thunderbolt: Add support for USB4STREAM
From: Andrew Lunn @ 2026-04-28 15:08 UTC (permalink / raw)
  To: Mika Westerberg
  Cc: linux-usb, Yehezkel Bernat, Lukas Wunner, Andreas Noever,
	Alan Borzeszkowski, Andrew Lunn, David S . Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, netdev
In-Reply-To: <20260428072209.3084930-10-mika.westerberg@linux.intel.com>

On Tue, Apr 28, 2026 at 09:22:09AM +0200, Mika Westerberg wrote:
> Introduce USB4STREAM protocol and Linux implementation. This allows two
> (or more) hosts to transfer data directly over Thunderbolt/USB4 cable
> through a character device without need to go through the network stack.

Is this mutually exclusive to networking, on a device?

   Andrew

^ permalink raw reply

* Re: [PATCH 9/9] thunderbolt: Add support for USB4STREAM
From: Mika Westerberg @ 2026-04-28 15:13 UTC (permalink / raw)
  To: Andrew Lunn
  Cc: linux-usb, Yehezkel Bernat, Lukas Wunner, Andreas Noever,
	Alan Borzeszkowski, Andrew Lunn, David S . Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, netdev
In-Reply-To: <ef5411f4-0e4c-4ac1-8ae0-0d557fc0ebd2@lunn.ch>

On Tue, Apr 28, 2026 at 05:08:55PM +0200, Andrew Lunn wrote:
> On Tue, Apr 28, 2026 at 09:22:09AM +0200, Mika Westerberg wrote:
> > Introduce USB4STREAM protocol and Linux implementation. This allows two
> > (or more) hosts to transfer data directly over Thunderbolt/USB4 cable
> > through a character device without need to go through the network stack.
> 
> Is this mutually exclusive to networking, on a device?

No - they can co-exist.

^ permalink raw reply

* Recommended local LLM-assisted review for netdev submissions?
From: Satish Kharat @ 2026-04-28 15:17 UTC (permalink / raw)
  To: Jakub Kicinski; +Cc: netdev, satishkh

Hi Jakub,

I'm the author of the in-flight enic SR-IOV series. Before posting
revisions I run checkpatch --strict, make W=1 C=1 on touched files,
scripts/kernel-doc -none, and `b4 prep --check`.

I've read Jesse's Netdev 0x18 talk on AI-enhanced reviews [1], the
LWN coverage [2][3], and looked at sashiko [4]. What I haven't been
able to tell from the public material:

  - Is sashiko (or a successor) what's currently producing the
    LLM-style review replies showing up on netdev, or is that a
    different pipeline?
  - For contributors who'd like to self-review with the same
    prompt/ruleset before posting, is there a recommended setup,
    or is sashiko-as-is the closest thing today?

Happy with a one-line pointer if it's already documented somewhere
I missed.

Thanks,
Satish

[1] https://netdevconf.info/0x18/sessions/talk/ai-enhanced-reviews-for-linux-networking.html
[2] https://lwn.net/Articles/987319/
[3] https://lwn.net/Articles/1032612/
[4] https://github.com/sashiko-dev/sashiko

^ permalink raw reply

* [PATCH iproute2-next 0/2] dpll: phase unit display and frequency monitoring
From: Ivan Vecera @ 2026-04-28 15:21 UTC (permalink / raw)
  To: netdev; +Cc: David Ahern, Stephen Hemminger, Petr Oros

This series improves dpll pin output formatting and adds support for
the frequency monitoring feature.

Patch 1 adds picosecond unit to phase-adjust-min, phase-adjust-max
and phase-adjust attributes. It also introduces the DPLL_PR_PHASE_OFFSET
macro that properly formats phase-offset as fractional picoseconds by
dividing the raw kernel value by DPLL_PHASE_OFFSET_DIVIDER.

Patch 2 adds support for the new frequency monitoring feature including
the DPLL_A_FREQUENCY_MONITOR device attribute and
DPLL_A_PIN_MEASURED_FREQUENCY pin attribute. The measured frequency is
displayed as fractional Hz using the DPLL_PR_MEASURED_FREQUENCY macro
since the kernel reports the value in millihertz. It also refactors
phase-offset-monitor parsing into a shared helper.

Tested on EDS2 development board with zl3073x DPLL:

  # dpll pin show package-label REF0P
  pin id 196:
    module-name: zl3073x
    clock-id: 13709406750444215013
    board-label: SyncE IN M1 CLK1
    package-label: REF0P
    type: synce-eth-port
    frequency: 125000000 Hz
    measured-frequency: 124999326.000 Hz
    frequency-supported:
      2500000 Hz
      25000000 Hz
      125000000 Hz
    capabilities: 0x6 state-can-change priority-can-change
    phase-adjust-min: -2147483648 ps
    phase-adjust-max: 2147483647 ps
    phase-adjust: 0 ps
    parent-device:
      id 14 direction input prio 10 state selectable phase-offset 0.000 ps
      id 15 direction input prio 0 state connected phase-offset 323.000 ps

Ivan Vecera (2):
  dpll: add ps unit to phase-related pin attributes
  dpll: add frequency monitoring support

 bash-completion/dpll |  4 +-
 dpll/dpll.c          | 89 +++++++++++++++++++++++++++++++++++---------
 man/man8/dpll.8      | 19 +++++++++-
 3 files changed, 90 insertions(+), 22 deletions(-)

-- 
2.53.0


^ permalink raw reply

* [PATCH iproute2-next 1/2] dpll: add ps unit to phase-related pin attributes
From: Ivan Vecera @ 2026-04-28 15:21 UTC (permalink / raw)
  To: netdev; +Cc: David Ahern, Stephen Hemminger, Petr Oros
In-Reply-To: <20260428152115.2815860-1-ivecera@redhat.com>

Display phase-adjust-min, phase-adjust-max and phase-adjust values
with ps unit. Add DPLL_PR_PHASE_OFFSET macro that properly formats
phase-offset as fractional picoseconds by dividing the raw kernel
value by DPLL_PHASE_OFFSET_DIVIDER.

Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
 dpll/dpll.c | 27 ++++++++++++++++++++++-----
 1 file changed, 22 insertions(+), 5 deletions(-)

diff --git a/dpll/dpll.c b/dpll/dpll.c
index b6ba3283e0ba..e8056ff6a24b 100644
--- a/dpll/dpll.c
+++ b/dpll/dpll.c
@@ -410,6 +410,21 @@ static __s64 mnl_attr_get_sint(const struct nlattr *attr)
 		}                                                              \
 	} while (0)
 
+/* Phase offset - JSON prints raw sub-ps value, FP prints fractional ps */
+#define DPLL_PR_PHASE_OFFSET(tb, attr_id)                                      \
+	do {                                                                   \
+		if (tb[attr_id]) {                                             \
+			__s64 val = mnl_attr_get_sint(tb[attr_id]);            \
+			lldiv_t d = lldiv(llabs(val),                          \
+					  DPLL_PHASE_OFFSET_DIVIDER);	       \
+			print_s64(PRINT_JSON, "phase-offset", NULL, val);      \
+			print_string(PRINT_FP, NULL, " phase-offset %s",       \
+				     val < 0 ? "-" : "");                      \
+			print_s64(PRINT_FP, NULL, "%lld.", d.quot);            \
+			print_s64(PRINT_FP, NULL, "%03lld ps", d.rem);         \
+		}                                                              \
+	} while (0)
+
 /* Generic version with custom format */
 #define DPLL_PR_ENUM_STR_FMT(tb, attr_id, name, format_str, name_func)         \
 	do {                                                                   \
@@ -1507,8 +1522,7 @@ static void dpll_pin_print_parent_devices(struct nlattr *attr)
 				 " prio %u");
 		DPLL_PR_ENUM_STR_FMT(tb_parent, DPLL_A_PIN_STATE, "state",
 				     " state %s", dpll_pin_state_name);
-		DPLL_PR_SINT_FMT(tb_parent, DPLL_A_PIN_PHASE_OFFSET,
-				 "phase-offset", " phase-offset %" PRId64);
+		DPLL_PR_PHASE_OFFSET(tb_parent, DPLL_A_PIN_PHASE_OFFSET);
 
 		print_nl();
 		close_json_object();
@@ -1592,10 +1606,13 @@ static void dpll_pin_print_attrs(struct nlattr **tb)
 
 	dpll_pin_print_capabilities(tb[DPLL_A_PIN_CAPABILITIES]);
 
-	DPLL_PR_INT(tb, DPLL_A_PIN_PHASE_ADJUST_MIN, "phase-adjust-min");
-	DPLL_PR_INT(tb, DPLL_A_PIN_PHASE_ADJUST_MAX, "phase-adjust-max");
+	DPLL_PR_INT_FMT(tb, DPLL_A_PIN_PHASE_ADJUST_MIN, "phase-adjust-min",
+			"  phase-adjust-min: %d ps\n");
+	DPLL_PR_INT_FMT(tb, DPLL_A_PIN_PHASE_ADJUST_MAX, "phase-adjust-max",
+			"  phase-adjust-max: %d ps\n");
 	DPLL_PR_UINT(tb, DPLL_A_PIN_PHASE_ADJUST_GRAN, "phase-adjust-gran");
-	DPLL_PR_INT(tb, DPLL_A_PIN_PHASE_ADJUST, "phase-adjust");
+	DPLL_PR_INT_FMT(tb, DPLL_A_PIN_PHASE_ADJUST, "phase-adjust",
+			"  phase-adjust: %d ps\n");
 
 	if (json || !tb[DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET_PPT])
 		DPLL_PR_SINT(tb, DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET,
-- 
2.53.0


^ permalink raw reply related

* [PATCH iproute2-next 2/2] dpll: add frequency monitoring support
From: Ivan Vecera @ 2026-04-28 15:21 UTC (permalink / raw)
  To: netdev; +Cc: David Ahern, Stephen Hemminger, Petr Oros
In-Reply-To: <20260428152115.2815860-1-ivecera@redhat.com>

Add support for the new frequency monitoring feature from the kernel
patch series "dpll: add actual frequency monitoring feature". This
includes:

- DPLL_A_FREQUENCY_MONITOR device attribute (enable/disable)
- DPLL_A_PIN_MEASURED_FREQUENCY pin attribute displayed as fractional Hz
  using DPLL_PR_MEASURED_FREQUENCY macro (kernel reports in mHz)
- device set: frequency-monitor { enable | disable }
- Refactor phase-offset-monitor to use new dpll_parse_attr_feature_state
  helper shared with frequency-monitor
- Update man page and bash-completion

Signed-off-by: Ivan Vecera <ivecera@redhat.com>
---
 bash-completion/dpll |  4 +--
 dpll/dpll.c          | 62 ++++++++++++++++++++++++++++++++++----------
 man/man8/dpll.8      | 19 ++++++++++++--
 3 files changed, 68 insertions(+), 17 deletions(-)

diff --git a/bash-completion/dpll b/bash-completion/dpll
index 542b99c2fce2..7ddcf529d429 100644
--- a/bash-completion/dpll
+++ b/bash-completion/dpll
@@ -100,7 +100,7 @@ _dpll_device()
                     COMPREPLY=( $( compgen -W "automatic manual" -- "$cur" ) )
                     return 0
                     ;;
-                phase-offset-monitor)
+                phase-offset-monitor|frequency-monitor)
                     COMPREPLY=( $( compgen -W "enable disable true false 0 1" -- "$cur" ) )
                     return 0
                     ;;
@@ -110,7 +110,7 @@ _dpll_device()
                     ;;
                 *)
                     COMPREPLY=( $( compgen -W "id mode phase-offset-monitor \
-                        phase-offset-avg-factor" -- "$cur" ) )
+                        phase-offset-avg-factor frequency-monitor" -- "$cur" ) )
                     return 0
                     ;;
             esac
diff --git a/dpll/dpll.c b/dpll/dpll.c
index e8056ff6a24b..6d8c0cbb8a34 100644
--- a/dpll/dpll.c
+++ b/dpll/dpll.c
@@ -313,6 +313,26 @@ static int dpll_parse_attr_str(struct dpll *dpll, struct nlmsghdr *nlh,
 	return 0;
 }
 
+static int dpll_parse_attr_feature_state(struct dpll *dpll,
+					struct nlmsghdr *nlh,
+					const char *arg_name, int attr_id)
+{
+	const char *str = dpll_argv_next(dpll);
+	bool val;
+
+	if (!str) {
+		pr_err("%s requires an argument\n", arg_name);
+		return -EINVAL;
+	}
+	if (str_to_bool(str, &val)) {
+		pr_err("invalid %s value: %s (use enable/disable)\n",
+		       arg_name, str);
+		return -EINVAL;
+	}
+	mnl_attr_put_u32(nlh, attr_id, val ? 1 : 0);
+	return 0;
+}
+
 static int dpll_parse_attr_enum(struct dpll *dpll, struct nlmsghdr *nlh,
 				const char *arg_name, int attr_id,
 				int (*parse_func)(struct dpll *, __u32 *))
@@ -425,6 +445,21 @@ static __s64 mnl_attr_get_sint(const struct nlattr *attr)
 		}                                                              \
 	} while (0)
 
+/* Measured frequency - JSON prints raw mHz value, FP prints fractional Hz */
+#define DPLL_PR_MEASURED_FREQUENCY(tb, attr_id)                                \
+	do {                                                                   \
+		if (tb[attr_id]) {                                             \
+			__u64 val = mnl_attr_get_u64(tb[attr_id]);             \
+			lldiv_t d = lldiv(val,                                 \
+				DPLL_PIN_MEASURED_FREQUENCY_DIVIDER);           \
+			print_lluint(PRINT_JSON, "measured-frequency",         \
+				     NULL, val);                                \
+			print_s64(PRINT_FP, NULL,                              \
+				  "  measured-frequency: %lld.", d.quot);       \
+			print_s64(PRINT_FP, NULL, "%03lld Hz\n", d.rem);       \
+		}                                                              \
+	} while (0)
+
 /* Generic version with custom format */
 #define DPLL_PR_ENUM_STR_FMT(tb, attr_id, name, format_str, name_func)         \
 	do {                                                                   \
@@ -657,6 +692,7 @@ static void cmd_device_help(void)
 	pr_err("       dpll device set id DEVICE_ID [ mode { automatic | manual } ]\n");
 	pr_err("                                    [ phase-offset-monitor { enable | disable } ]\n");
 	pr_err("                                    [ phase-offset-avg-factor NUM ]\n");
+	pr_err("                                    [ frequency-monitor { enable | disable } ]\n");
 	pr_err("       dpll device id-get [ module-name NAME ] [ clock-id ID ] [ type TYPE ]\n");
 }
 
@@ -1058,6 +1094,10 @@ static void dpll_device_print_attrs(const struct nlmsghdr *nlh,
 			     str_enable_disable);
 	DPLL_PR_UINT(tb, DPLL_A_PHASE_OFFSET_AVG_FACTOR,
 		     "phase-offset-avg-factor");
+	DPLL_PR_ENUM_STR_FMT(tb, DPLL_A_FREQUENCY_MONITOR,
+			     "frequency-monitor",
+			     "  frequency-monitor: %s\n",
+			     str_enable_disable);
 }
 
 /* Netlink callback - device get (single device) */
@@ -1219,25 +1259,20 @@ static int cmd_device_set(struct dpll *dpll)
 						 dpll_parse_mode))
 				return -EINVAL;
 		} else if (dpll_argv_match(dpll, "phase-offset-monitor")) {
-			const char *str = dpll_argv_next(dpll);
-			bool val;
-
-			if (!str) {
-				pr_err("phase-offset-monitor requires an argument\n");
-				return -EINVAL;
-			}
-			if (str_to_bool(str, &val)) {
-				pr_err("invalid phase-offset-monitor value: %s (use enable/disable)\n",
-				       str);
+			if (dpll_parse_attr_feature_state(dpll, nlh,
+							  "phase-offset-monitor",
+							  DPLL_A_PHASE_OFFSET_MONITOR))
 				return -EINVAL;
-			}
-			mnl_attr_put_u32(nlh, DPLL_A_PHASE_OFFSET_MONITOR,
-					 val ? 1 : 0);
 		} else if (dpll_argv_match(dpll, "phase-offset-avg-factor")) {
 			if (dpll_parse_attr_u32(dpll, nlh,
 						"phase-offset-avg-factor",
 						DPLL_A_PHASE_OFFSET_AVG_FACTOR))
 				return -EINVAL;
+		} else if (dpll_argv_match(dpll, "frequency-monitor")) {
+			if (dpll_parse_attr_feature_state(dpll, nlh,
+							  "frequency-monitor",
+							  DPLL_A_FREQUENCY_MONITOR))
+				return -EINVAL;
 		} else {
 			pr_err("unknown option: %s\n", dpll_argv(dpll));
 			return -EINVAL;
@@ -1601,6 +1636,7 @@ static void dpll_pin_print_attrs(struct nlattr **tb)
 	DPLL_PR_ENUM_STR(tb, DPLL_A_PIN_TYPE, "type", dpll_pin_type_name);
 	DPLL_PR_U64_FMT(tb, DPLL_A_PIN_FREQUENCY, "frequency",
 			"  frequency: %" PRIu64 " Hz\n");
+	DPLL_PR_MEASURED_FREQUENCY(tb, DPLL_A_PIN_MEASURED_FREQUENCY);
 
 	dpll_pin_print_freq_supported(tb[DPLL_A_PIN_FREQUENCY_SUPPORTED]);
 
diff --git a/man/man8/dpll.8 b/man/man8/dpll.8
index 89f17af74923..59ec4208f251 100644
--- a/man/man8/dpll.8
+++ b/man/man8/dpll.8
@@ -111,7 +111,7 @@ Temperature (if supported)
 Type (PPS or EEC)
 .RE
 
-.SS dpll device set id ID [ mode { automatic | manual } ] [ phase-offset-monitor { enable | disable } ] [ phase-offset-avg-factor FACTOR ]
+.SS dpll device set id ID [ mode { automatic | manual } ] [ phase-offset-monitor { enable | disable } ] [ phase-offset-avg-factor FACTOR ] [ frequency-monitor { enable | disable } ]
 
 Configure DPLL device parameters.
 
@@ -140,6 +140,14 @@ When enabled, the kernel continuously measures and reports phase differences.
 Set the averaging factor (1-255) applied to phase offset calculations.
 Higher values provide smoother but slower-responding measurements.
 
+.TP
+.BI frequency-monitor " { enable | disable | true | false | 0 | 1 }"
+Enable or disable frequency monitoring on the device. When enabled, the
+kernel continuously measures and reports actual pin frequencies, which can
+be read via the
+.B measured-frequency
+field in pin show output.
+
 .SS dpll device id-get [ module-name NAME ] [ clock-id ID ] [ type TYPE ]
 
 Retrieve the device ID based on identifying attributes. Useful for scripting
@@ -233,7 +241,9 @@ Board label (hardware label from device tree or ACPI)
 .IP \[bu]
 Pin type (mux, ext, synce-eth-port, int-oscillator, gnss)
 .IP \[bu]
-Frequency and supported frequency ranges
+Configured frequency and supported frequency ranges
+.IP \[bu]
+Measured frequency in Hz (when frequency monitoring is enabled)
 .IP \[bu]
 Capabilities (state-can-change, priority-can-change, direction-can-change)
 .IP \[bu]
@@ -372,6 +382,11 @@ Press Ctrl+C to stop monitoring.
 .B dpll device set id 0 phase-offset-monitor enable
 .fi
 
+.SS Enable frequency monitoring on device 0
+.nf
+.B dpll device set id 0 frequency-monitor enable
+.fi
+
 .SS Show all EEC devices
 .nf
 .B dpll device show type eec
-- 
2.53.0


^ permalink raw reply related

* Re: [PATCH net-next] ipvlan: use netif_receive_skb() in ipvlan_process_multicast()
From: Eric Dumazet @ 2026-04-28 15:23 UTC (permalink / raw)
  To: David S . Miller, Jakub Kicinski, Paolo Abeni
  Cc: Simon Horman, netdev, eric.dumazet
In-Reply-To: <20260427093633.979310-1-edumazet@google.com>

On Mon, Apr 27, 2026 at 2:36 AM Eric Dumazet <edumazet@google.com> wrote:
>
> ipvlan_process_multicast() runs from process context, there is no
> risk of stack overflow if we call netif_receive_skb() instead
> of netif_rx().
>
> This avoids some overhead adding/removing skbs to/from a per-cpu
> backlog and raising/processing NET_RX softirqs.
>
> Signed-off-by: Eric Dumazet <edumazet@google.com>
> ---
>  drivers/net/ipvlan/ipvlan_core.c | 2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/drivers/net/ipvlan/ipvlan_core.c b/drivers/net/ipvlan/ipvlan_core.c
> index 1be8620ad3971d281fb36fd0770efd67b566ae60..7ad12dc7845c836bdd04395f153b2b4b397797d6 100644
> --- a/drivers/net/ipvlan/ipvlan_core.c
> +++ b/drivers/net/ipvlan/ipvlan_core.c
> @@ -286,7 +286,7 @@ void ipvlan_process_multicast(struct work_struct *work)
>                                 if (tx_pkt)
>                                         ret = dev_forward_skb(ipvlan->dev, nskb);
>                                 else
> -                                       ret = netif_rx(nskb);
> +                                       ret = netif_receive_skb(nskb);
>                         }
>                         ipvlan_count_rx(ipvlan, len, ret == NET_RX_SUCCESS, true);
>                         local_bh_enable();

I will send a V2, to limit the number of netif_receive_skb() in
presence of large number of ipvlans.

pw-bot: cr

^ permalink raw reply

* Re: (subset) [PATCH V4 0/3] net: stmmac: Fix Tegra234 MGBE clock
From: Thierry Reding @ 2026-04-28 15:24 UTC (permalink / raw)
  To: Andrew Lunn, David S . Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Thierry Reding, Jon Hunter
  Cc: netdev, devicetree, linux-tegra
In-Reply-To: <20260401102941.17466-1-jonathanh@nvidia.com>

From: Thierry Reding <treding@nvidia.com>


On Wed, 01 Apr 2026 11:29:38 +0100, Jon Hunter wrote:
> The name of the PTP ref clock for the Tegra234 MGBE ethernet controller
> does not match the generic name in the stmmac platform driver. Despite
> this basic ethernet is functional on the Tegra234 platforms that use
> this driver and as far as I know, we have not tested PTP support with
> this driver. Hence, the risk of breaking any functionality is low.
> 
> The previous attempt to fix this in the stmmac platform driver, by
> supporting the Tegra234 PTP clock name, was rejected [0]. The preference
> from the netdev maintainers is to fix this in the DT binding for
> Tegra234.
> 
> [...]

Applied, thanks!

[3/3] arm64: tegra: Fix Tegra234 MGBE PTP clock
      commit: 8f0cc929a4bad534c5a860a53d88912cf16d9c9c

Best regards,
-- 
Thierry Reding <treding@nvidia.com>

^ permalink raw reply

* Re: Recommended local LLM-assisted review for netdev submissions?
From: Jakub Kicinski @ 2026-04-28 15:27 UTC (permalink / raw)
  To: Satish Kharat; +Cc: netdev
In-Reply-To: <20260428151755.1310712-1-satishkh@cisco.com>

On Tue, 28 Apr 2026 08:17:55 -0700 Satish Kharat wrote:
> I'm the author of the in-flight enic SR-IOV series. Before posting
> revisions I run checkpatch --strict, make W=1 C=1 on touched files,
> scripts/kernel-doc -none, and `b4 prep --check`.
> 
> I've read Jesse's Netdev 0x18 talk on AI-enhanced reviews [1], the
> LWN coverage [2][3], and looked at sashiko [4]. What I haven't been
> able to tell from the public material:
> 
>   - Is sashiko (or a successor) what's currently producing the
>     LLM-style review replies showing up on netdev, or is that a
>     different pipeline?
>   - For contributors who'd like to self-review with the same
>     prompt/ruleset before posting, is there a recommended setup,
>     or is sashiko-as-is the closest thing today?

Our current recommendation for "individual" contributors is to use
the review prompts directly:
https://netdev-ai.bots.linux.dev/ai-local.html

If you have / can get LLM API keys sashiko seems to be producing
better results (although not strictly better). Ideally you'd want
to run Sashiko on all three of the major LLM models and then another
sweep to coalesce the results. We can't do this at netdev today because
it's cost prohibitive. But Google's Sashiko runs on Gemini, ours runs
on Claude and they find different things..

I tried to improve Sashiko + CLI models but from my limited
experimentation it's worse than just using the review prompts.

> Happy with a one-line pointer if it's already documented somewhere
> I missed.

Sorry for not having the source of truth. The space is rapidly
evolving :(

^ permalink raw reply

* Re: [PATCH net 11/11] ipvs: fix the spin_lock usage for RT build
From: Julian Anastasov @ 2026-04-28 15:27 UTC (permalink / raw)
  To: Jakub Kicinski
  Cc: pablo, netfilter-devel, davem, netdev, pabeni, edumazet, fw,
	horms
In-Reply-To: <20260428021343.793695-1-kuba@kernel.org>


	Hello,

On Mon, 27 Apr 2026, Jakub Kicinski wrote:

> This is an AI-generated review of your patch. The human sending this
> email has considered the AI review valid, or at least plausible.
> ---
> ipvs: fix the spin_lock usage for RT build
> 
> This commit fixes spin_lock sleeping issues on PREEMPT_RT by adjusting the
> use of cp->lock in combination with hlist_bl bit locks. It removes the
> spin_lock from ip_vs_conn_hash() and ip_vs_conn_unlink(), and changes the
> lock ordering in ip_vs_conn_fill_cport().
> 
> Link: https://sashiko.dev/#/patchset/20260415200216.79699-1-ja%40ssi.bg
> Link: https://sashiko.dev/#/patchset/20260420165539.85174-4-ja%40ssi.bg
> Link: https://sashiko.dev/#/patchset/20260422135823.50489-4-ja%40ssi.bg
> 
> > diff --git a/net/netfilter/ipvs/ip_vs_conn.c b/net/netfilter/ipvs/ip_vs_conn.c
> > index 84a4921a7865a9..9e23cda8482588 100644
> > --- a/net/netfilter/ipvs/ip_vs_conn.c
> > +++ b/net/netfilter/ipvs/ip_vs_conn.c
> 
> [ ... ]
> 
> > @@ -704,14 +702,23 @@ void ip_vs_conn_fill_cport(struct ip_vs_conn *cp, __be16 cport)
> >  		hlist_bl_unlock(head);
> >  		write_seqcount_end(&t->seqc[hash_key & t->seqc_mask]);
> >  		preempt_enable_nested();
> > -		spin_unlock_bh(&t->lock[hash_key & t->lock_mask].l);
> > +		spin_unlock(&t->lock[hash_key & t->lock_mask].l);
> > +		spin_unlock_bh(&cp->lock);
> >  		hash_key = hash_key_new;
> >  		goto retry;
> >  	}
> >  
> > -	spin_lock(&cp->lock);
> > -	if ((cp->flags & IP_VS_CONN_F_NO_CPORT) &&
> > -	    (cp->flags & IP_VS_CONN_F_HASHED)) {
> > +	/* Fill cport once, even if multiple packets try to do it */
> > +	if (cp->flags & IP_VS_CONN_F_NO_CPORT && (!cp->cport || by_me)) {
> 
> Does removing the IP_VS_CONN_F_HASHED check here lead to a NULL pointer
> dereference for IP_VS_CONN_F_ONE_PACKET connections?
> 
> Connections flagged with IP_VS_CONN_F_ONE_PACKET bypass hashing:
> 
> ip_vs_conn_hash() {
>     if (cp->flags & IP_VS_CONN_F_ONE_PACKET)
>         return 0;
>     ...
> }
> 
> Because they bypass hashing, their hash list nodes (hn->node) are never
> linked, leaving hn->node.pprev as NULL. If such a connection is created
> with a source port of 0, it gets the IP_VS_CONN_F_NO_CPORT flag.
> 
> When ip_vs_conn_fill_cport() is later invoked to populate the port, the
> missing IP_VS_CONN_F_HASHED check allows the execution to proceed.

	But ip_vs_conn_fill_cport() is called for connection
that is hashed and later found by lookup. If IP_VS_CONN_F_ONE_PACKET
is set the conn is not hashed and is released immediately after
creation by ip_vs_conn_put(). Both flags can not coexist, one or
the other wins.

	As for IP_VS_CONN_F_NO_CPORT, ip_vs_bind_dest() clears the
IP_VS_CONN_F_ONE_PACKET flag if not UDP and we use NO_CPORT for TCP.
So, IP_VS_CONN_F_ONE_PACKET is simply ignored for TCP. If one day
we start to use IP_VS_CONN_F_NO_CPORT for UDP and use
IP_VS_CONN_F_ONE_PACKET then it will not work because the conn will
not be hashed and as result never found to fill the cport.
This time IP_VS_CONN_F_ONE_PACKET will win the game.

> 
> > +		/* If we race with resizing make sure cport is set for dir 1 */
> > +		if (!cp->cport) {
> > +			cp->cport = cport;
> > +			by_me = true;
> > +		}
> > +		if (!dir) {
> > +			atomic_dec(&ipvs->no_cport_conns[af_id]);
> > +			cp->flags &= ~IP_VS_CONN_F_NO_CPORT;
> > +		}
> >  		/* We do not recalc hash_key_r under lock, we assume the
> >  		 * parameters in cp do not change, i.e. cport is
> >  		 * the only possible change.
> > @@ -726,21 +733,17 @@ void ip_vs_conn_fill_cport(struct ip_vs_conn *cp, __be16 cport)
> >  			hlist_bl_del_rcu(&hn->node);
> 
> If head != head2 here, hlist_bl_del_rcu(&hn->node) is called. Since
> hn->node.pprev is NULL, wouldn't this cause a panic when __hlist_bl_del()
> executes *pprev = next?
> 
> Is it possible to retain the IP_VS_CONN_F_HASHED check or otherwise
> ensure unhashed nodes aren't unlinked?

	As IP_VS_CONN_F_ONE_PACKET conns are not hashed,
ip_vs_conn_unlink() has check not to unlink the conn nodes from
tables. The conn is invisible to others except the packet
that created it. And the hash nodes are not touched.

Regards

--
Julian Anastasov <ja@ssi.bg>


^ permalink raw reply

* Re: [PATCH net-next] ipvlan: use netif_receive_skb() in ipvlan_process_multicast()
From: Eric Dumazet @ 2026-04-28 15:33 UTC (permalink / raw)
  To: David S . Miller, Jakub Kicinski, Paolo Abeni
  Cc: Simon Horman, netdev, eric.dumazet
In-Reply-To: <CANn89iKtmRcw2RsoofCnSf7kQhBWMYNhjkhSyqdaPcuQw3ZyyA@mail.gmail.com>

On Tue, Apr 28, 2026 at 8:23 AM Eric Dumazet <edumazet@google.com> wrote:
>
> On Mon, Apr 27, 2026 at 2:36 AM Eric Dumazet <edumazet@google.com> wrote:
> >
> > ipvlan_process_multicast() runs from process context, there is no
> > risk of stack overflow if we call netif_receive_skb() instead
> > of netif_rx().
> >
> > This avoids some overhead adding/removing skbs to/from a per-cpu
> > backlog and raising/processing NET_RX softirqs.
> >
> > Signed-off-by: Eric Dumazet <edumazet@google.com>
> > ---
> >  drivers/net/ipvlan/ipvlan_core.c | 2 +-
> >  1 file changed, 1 insertion(+), 1 deletion(-)
> >
> > diff --git a/drivers/net/ipvlan/ipvlan_core.c b/drivers/net/ipvlan/ipvlan_core.c
> > index 1be8620ad3971d281fb36fd0770efd67b566ae60..7ad12dc7845c836bdd04395f153b2b4b397797d6 100644
> > --- a/drivers/net/ipvlan/ipvlan_core.c
> > +++ b/drivers/net/ipvlan/ipvlan_core.c
> > @@ -286,7 +286,7 @@ void ipvlan_process_multicast(struct work_struct *work)
> >                                 if (tx_pkt)
> >                                         ret = dev_forward_skb(ipvlan->dev, nskb);
> >                                 else
> > -                                       ret = netif_rx(nskb);
> > +                                       ret = netif_receive_skb(nskb);
> >                         }
> >                         ipvlan_count_rx(ipvlan, len, ret == NET_RX_SUCCESS, true);
> >                         local_bh_enable();
>
> I will send a V2, to limit the number of netif_receive_skb() in
> presence of large number of ipvlans.

Well, this was caused this sashiko report:

Because netif_receive_skb() traverses the networking stack synchronously, it
bypasses the backlog and softirq processing budgets. In environments with many
IPVLAN sub-interfaces, delivering a broadcast or multicast packet could
monopolize the CPU for an unbounded amount of time before reaching
rcu_read_unlock().

But using netif_rx(nskb) had the same problem.
Each packet was immediately processed at the local_bh_enable() time.

So I do not think a V2 is needed.

^ permalink raw reply

* [PATCH iwl-next 0/3] virtchnl: add extended VF capability flags
From: Marcin Szycik @ 2026-04-28 14:37 UTC (permalink / raw)
  To: intel-wired-lan; +Cc: netdev, paul.greenwalt, jacob.e.keller, Marcin Szycik

VF capability flags in struct virtchnl_vf_resource::vf_cap_flags have
all been used up, preventing new flags from being added. Unfortunately,
flags size cannot be easily increased, as virtchnl message size and structure
must remain unchanged to not break ABI. Instead, add a new virtchnl
message to handle passing extended caps. Make it flexible size, so new
flags can be freely added in the future, without changing virtchnl
interface.

Overview:
1: Clean up unused defines that are in the way
2: Do most of the mechanical changes required to store flags in bitmaps
3: Add the new virtchnl message

Marcin Szycik (3):
  virtchnl: remove unused defines
  virtchnl, iavf, ice, i40e: store VIRTCHNL_VF_* flags in bitmaps
  virtchnl, iavf, ice, i40e: add extended generic VF capability flags

 .../ethernet/intel/i40e/i40e_virtchnl_pf.h    |   4 +-
 drivers/net/ethernet/intel/iavf/iavf.h        |  68 +++---
 .../net/ethernet/intel/iavf/iavf_prototype.h  |   3 +-
 drivers/net/ethernet/intel/ice/ice_vf_lib.h   |   5 +-
 .../net/ethernet/intel/ice/virt/virtchnl.h    |   2 +
 include/linux/intel/virtchnl.h                | 120 ++++++++---
 .../ethernet/intel/i40e/i40e_virtchnl_pf.c    | 160 ++++++++++----
 drivers/net/ethernet/intel/iavf/iavf_common.c |   7 +-
 .../net/ethernet/intel/iavf/iavf_ethtool.c    |   2 +-
 drivers/net/ethernet/intel/iavf/iavf_main.c   |  96 +++++++--
 .../net/ethernet/intel/iavf/iavf_virtchnl.c   | 185 ++++++++++++++---
 drivers/net/ethernet/intel/ice/ice_vf_lib.c   |   4 +-
 .../net/ethernet/intel/ice/virt/allowlist.c   |  11 +-
 drivers/net/ethernet/intel/ice/virt/fdir.c    |   2 +-
 drivers/net/ethernet/intel/ice/virt/queues.c  |  13 +-
 drivers/net/ethernet/intel/ice/virt/rss.c     |   4 +-
 .../net/ethernet/intel/ice/virt/virtchnl.c    | 195 +++++++++++++-----
 17 files changed, 667 insertions(+), 214 deletions(-)

-- 
2.49.0


^ permalink raw reply

* [PATCH iwl-next 1/3] virtchnl: remove unused defines
From: Marcin Szycik @ 2026-04-28 14:37 UTC (permalink / raw)
  To: intel-wired-lan
  Cc: netdev, paul.greenwalt, jacob.e.keller, Marcin Szycik,
	Aleksandr Loktionov, Przemek Kitszel
In-Reply-To: <20260428143716.9653-1-marcin.szycik@linux.intel.com>

Remove:
* VIRTCHNL_VF_CAP_RDMA
* VF_BASE_MODE_OFFLOADS

Signed-off-by: Marcin Szycik <marcin.szycik@linux.intel.com>
Reviewed-by: Jacob Keller <jacob.e.keller@intel.com>
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
Reviewed-by: Przemek Kitszel <przemyslaw.kitszel@intel.com>
---
 include/linux/intel/virtchnl.h | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/include/linux/intel/virtchnl.h b/include/linux/intel/virtchnl.h
index 511c8827c640..bb4737de37fe 100644
--- a/include/linux/intel/virtchnl.h
+++ b/include/linux/intel/virtchnl.h
@@ -250,7 +250,6 @@ VIRTCHNL_CHECK_STRUCT_LEN(16, virtchnl_vsi_resource);
  */
 #define VIRTCHNL_VF_OFFLOAD_L2			BIT(0)
 #define VIRTCHNL_VF_OFFLOAD_RDMA		BIT(1)
-#define VIRTCHNL_VF_CAP_RDMA			VIRTCHNL_VF_OFFLOAD_RDMA
 #define VIRTCHNL_VF_OFFLOAD_RSS_AQ		BIT(3)
 #define VIRTCHNL_VF_OFFLOAD_RSS_REG		BIT(4)
 #define VIRTCHNL_VF_OFFLOAD_WB_ON_ITR		BIT(5)
@@ -275,10 +274,6 @@ VIRTCHNL_CHECK_STRUCT_LEN(16, virtchnl_vsi_resource);
 #define VIRTCHNL_VF_OFFLOAD_QOS			BIT(29)
 #define VIRTCHNL_VF_CAP_PTP			BIT(31)
 
-#define VF_BASE_MODE_OFFLOADS (VIRTCHNL_VF_OFFLOAD_L2 | \
-			       VIRTCHNL_VF_OFFLOAD_VLAN | \
-			       VIRTCHNL_VF_OFFLOAD_RSS_PF)
-
 struct virtchnl_vf_resource {
 	u16 num_vsis;
 	u16 num_queue_pairs;
-- 
2.49.0


^ permalink raw reply related

* [PATCH iwl-next 3/3] virtchnl, iavf, ice, i40e: add extended generic VF capability flags
From: Marcin Szycik @ 2026-04-28 14:37 UTC (permalink / raw)
  To: intel-wired-lan
  Cc: netdev, paul.greenwalt, jacob.e.keller, Marcin Szycik,
	Przemek Kitszel
In-Reply-To: <20260428143716.9653-1-marcin.szycik@linux.intel.com>

VF capability flags in struct virtchnl_vf_resource::vf_cap_flags have
all been used up, preventing new flags from being added. Note that
despite not all bits being defined here, they are used by out-of-tree
releases of Intel drivers, therefore cannot be taken.

virtchnl message size and structure must remain unchanged to not break
reverse compatibility, therefore the existing virtchnl structure cannot
be extended with additional fields (e.g. flags2). vf_cap_flags type
cannot be changed to a larger one for the same reason.

Bit 2 of vf_cap_flags was reserved for exactly this case. Its presence
in message initially sent from VF shall now signal that there are more
capability flags to be parsed. If the PF driver acknowledges that via
VIRTCHNL_OP_GET_VF_RESOURCES response, the VF will send a separate
message: VIRTCHNL_OP_GET_VF_CAPS2, containing more capability flags.
Note: this mechanism is similar for VIRTCHNL_OP_1588_PTP_GET_CAPS.

The new message supports flexible size, so more flags can be added
without any architectural changes. Care was taken to ensure that no
out-of-bounds reads happen in case the bitmap is shorter in one of the
drivers.

The new message includes the original 32 bits too, for consistency and
more straightforward parsing.

Signed-off-by: Marcin Szycik <marcin.szycik@linux.intel.com>
Reviewed-by: Przemek Kitszel <przemyslaw.kitszel@intel.com>
Reviewed-by: Jacob Keller <jacob.e.keller@intel.com>
---
 drivers/net/ethernet/intel/iavf/iavf.h        |  19 ++-
 .../net/ethernet/intel/ice/virt/virtchnl.h    |   2 +
 include/linux/intel/virtchnl.h                |  55 ++++++-
 .../ethernet/intel/i40e/i40e_virtchnl_pf.c    |  84 +++++++++++
 drivers/net/ethernet/intel/iavf/iavf_main.c   |  60 ++++++++
 .../net/ethernet/intel/iavf/iavf_virtchnl.c   | 138 +++++++++++++++++-
 .../net/ethernet/intel/ice/virt/allowlist.c   |   6 +
 .../net/ethernet/intel/ice/virt/virtchnl.c    |  86 +++++++++++
 8 files changed, 444 insertions(+), 6 deletions(-)

diff --git a/drivers/net/ethernet/intel/iavf/iavf.h b/drivers/net/ethernet/intel/iavf/iavf.h
index 64576cba3a01..5d812b0a52a3 100644
--- a/drivers/net/ethernet/intel/iavf/iavf.h
+++ b/drivers/net/ethernet/intel/iavf/iavf.h
@@ -343,6 +343,7 @@ struct iavf_adapter {
 #define IAVF_FLAG_AQ_GET_SUPPORTED_RXDIDS		BIT_ULL(42)
 #define IAVF_FLAG_AQ_GET_PTP_CAPS			BIT_ULL(43)
 #define IAVF_FLAG_AQ_SEND_PTP_CMD			BIT_ULL(44)
+#define IAVF_FLAG_AQ_GET_VF_CAP_CAPS2			BIT_ULL(45)
 
 	/* AQ messages that must be sent after IAVF_FLAG_AQ_GET_CONFIG, in
 	 * order to negotiated extended capabilities.
@@ -350,7 +351,9 @@ struct iavf_adapter {
 #define IAVF_FLAG_AQ_EXTENDED_CAPS			\
 	(IAVF_FLAG_AQ_GET_OFFLOAD_VLAN_V2_CAPS |	\
 	 IAVF_FLAG_AQ_GET_SUPPORTED_RXDIDS |		\
-	 IAVF_FLAG_AQ_GET_PTP_CAPS)
+	 IAVF_FLAG_AQ_GET_PTP_CAPS |			\
+	 IAVF_FLAG_AQ_GET_VF_CAP_CAPS2 |		\
+	 0)
 
 	/* flags for processing extended capability messages during
 	 * __IAVF_INIT_EXTENDED_CAPS. Each capability exchange requires
@@ -366,6 +369,8 @@ struct iavf_adapter {
 #define IAVF_EXTENDED_CAP_RECV_RXDID			BIT_ULL(3)
 #define IAVF_EXTENDED_CAP_SEND_PTP			BIT_ULL(4)
 #define IAVF_EXTENDED_CAP_RECV_PTP			BIT_ULL(5)
+#define IAVF_EXTENDED_CAP_SEND_CAPS2			BIT_ULL(6)
+#define IAVF_EXTENDED_CAP_RECV_CAPS2			BIT_ULL(7)
 
 #define IAVF_EXTENDED_CAPS				\
 	(IAVF_EXTENDED_CAP_SEND_VLAN_V2 |		\
@@ -373,7 +378,10 @@ struct iavf_adapter {
 	 IAVF_EXTENDED_CAP_SEND_RXDID |			\
 	 IAVF_EXTENDED_CAP_RECV_RXDID |			\
 	 IAVF_EXTENDED_CAP_SEND_PTP |			\
-	 IAVF_EXTENDED_CAP_RECV_PTP)
+	 IAVF_EXTENDED_CAP_RECV_PTP |			\
+	 IAVF_EXTENDED_CAP_SEND_CAPS2 |			\
+	 IAVF_EXTENDED_CAP_RECV_CAPS2 |			\
+	 0)
 
 	/* Lock to prevent possible clobbering of
 	 * current_netdev_promisc_flags
@@ -430,6 +438,7 @@ struct iavf_adapter {
 #define IAVF_RXDID_ALLOWED(a)						\
 	test_bit(VIRTCHNL_VF_OFFLOAD_RX_FLEX_DESC, (a)->vf_cap_flags)
 #define IAVF_PTP_ALLOWED(a) test_bit(VIRTCHNL_VF_CAP_PTP, (a)->vf_cap_flags)
+#define IAVF_CAPS2_ALLOWED(a) test_bit(VIRTCHNL_VF_CAPS2, (a)->vf_cap_flags)
 	struct virtchnl_vf_resource *vf_res; /* incl. all VSIs */
 	struct virtchnl_vsi_resource *vsi_res; /* our LAN VSI */
 	struct virtchnl_version_info pf_version;
@@ -439,7 +448,9 @@ struct iavf_adapter {
 	u64 supp_rxdids;
 	struct iavf_ptp ptp;
 
-	/* Mirrors vf_res->vf_cap_flags */
+	/* Negotiated via VIRTCHNL_OP_GET_VF_CAPS2. First 32 bits mirror
+	 * vf_res->vf_cap_flags.
+	 */
 	DECLARE_BITMAP(vf_cap_flags, VIRTCHNL_VF_CAPS_MAX);
 
 	u16 msg_enable;
@@ -578,6 +589,8 @@ int iavf_send_vf_supported_rxdids_msg(struct iavf_adapter *adapter);
 int iavf_get_vf_supported_rxdids(struct iavf_adapter *adapter);
 int iavf_send_vf_ptp_caps_msg(struct iavf_adapter *adapter);
 int iavf_get_vf_ptp_caps(struct iavf_adapter *adapter);
+int iavf_send_vf_caps2_msg(struct iavf_adapter *adapter);
+int iavf_get_vf_caps2(struct iavf_adapter *adapter);
 void iavf_set_queue_vlan_tag_loc(struct iavf_adapter *adapter);
 u16 iavf_get_num_vlans_added(struct iavf_adapter *adapter);
 void iavf_irq_enable(struct iavf_adapter *adapter, bool flush);
diff --git a/drivers/net/ethernet/intel/ice/virt/virtchnl.h b/drivers/net/ethernet/intel/ice/virt/virtchnl.h
index f7f909424098..5b51a5a73708 100644
--- a/drivers/net/ethernet/intel/ice/virt/virtchnl.h
+++ b/drivers/net/ethernet/intel/ice/virt/virtchnl.h
@@ -78,6 +78,8 @@ struct ice_virtchnl_ops {
 	int (*get_ptp_cap)(struct ice_vf *vf,
 			   const struct virtchnl_ptp_caps *msg);
 	int (*get_phc_time)(struct ice_vf *vf);
+	int (*get_vf_caps2)(struct ice_vf *vf,
+			    const struct virtchnl_vf_caps2 *msg);
 };
 
 #ifdef CONFIG_PCI_IOV
diff --git a/include/linux/intel/virtchnl.h b/include/linux/intel/virtchnl.h
index 577b0549b437..edaca5ec8fd8 100644
--- a/include/linux/intel/virtchnl.h
+++ b/include/linux/intel/virtchnl.h
@@ -162,6 +162,8 @@ enum virtchnl_ops {
 	/* opcode 68 through 111 are reserved */
 	VIRTCHNL_OP_CONFIG_QUEUE_BW = 112,
 	VIRTCHNL_OP_CONFIG_QUANTA = 113,
+	/* opcode 114 through 135 are reserved */
+	VIRTCHNL_OP_GET_VF_CAPS2 = 136,
 	VIRTCHNL_OP_MAX,
 };
 
@@ -244,13 +246,14 @@ struct virtchnl_vsi_resource {
 
 VIRTCHNL_CHECK_STRUCT_LEN(16, virtchnl_vsi_resource);
 
-/* VF capability flags */
+/* Base (first 32) + extended VF capability flags */
 enum virtchnl_vf_caps_bitnr {
 	/* Inclusive of base mode L2 offloads including TX/RX Checksum
 	 * offloading and TSO for non-tunnelled packets.
 	 */
 	VIRTCHNL_VF_OFFLOAD_L2 = 0,
 	VIRTCHNL_VF_OFFLOAD_RDMA = 1,
+	VIRTCHNL_VF_CAPS2 = 2,
 	VIRTCHNL_VF_OFFLOAD_RSS_AQ = 3,
 	VIRTCHNL_VF_OFFLOAD_RSS_REG = 4,
 	VIRTCHNL_VF_OFFLOAD_WB_ON_ITR = 5,
@@ -275,6 +278,8 @@ enum virtchnl_vf_caps_bitnr {
 	VIRTCHNL_VF_OFFLOAD_QOS = 29,
 	VIRTCHNL_VF_CAP_PTP = 31,
 
+	/* Flags greater than 31 are only available via virtchnl_vf_caps2 */
+
 	VIRTCHNL_VF_CAPS_MAX /* must be last */
 };
 
@@ -284,7 +289,14 @@ struct virtchnl_vf_resource {
 	u16 max_vectors;
 	u16 max_mtu;
 
+	/* If both sides support VIRTCHNL_VF_CAPS2, extended flags, along with
+	 * flags from this structure will be sent in VIRTCHNL_OP_GET_VF_CAPS2.
+	 * Drivers should store capabilities negotiated this way in a bitmap,
+	 * instead of directly reading flags from this structure, in order to
+	 * have a unified interface for checking capabilities.
+	 */
 	u32 vf_cap_flags;
+
 	u32 rss_key_size;
 	u32 rss_lut_size;
 
@@ -1572,6 +1584,32 @@ struct virtchnl_ptp_caps {
 
 VIRTCHNL_CHECK_STRUCT_LEN(48, virtchnl_ptp_caps);
 
+/**
+ * virtchnl_vf_caps2 - generic extended capability flags
+ *
+ * VF sends this message to negotiate an extended set of capability flags if
+ * VIRTCHNL_VF_CAPS2 is set in virtchnl_vf_resource::vf_cap_flags.
+ *
+ * On send, VF sets what capabilities it requests. On reply, PF indicates what
+ * has been enabled for this VF. The PF shall not set bits which were not
+ * requested by the VF.
+ *
+ * Defines capabilities available to the VF, including the 32 original ones from
+ * vf_cap_flags on the same positions, plus the ones that didn't fit there.
+ * Drivers should construct a bitmap from this message, instead of checking the
+ * first 32 bits sent in VIRTCHNL_OP_GET_VF_RESOURCES, for consistency.
+ *
+ * The VF sends VIRTCHNL_OP_GET_VF_CAPS2 and fills the vf_cap_flags bitmap,
+ * indicating what capabilities it is requesting. The PF responds with the same
+ * message, indicating what is enabled for the VF.
+ */
+struct virtchnl_vf_caps2 {
+	u32 flags_len;
+	u32 vf_cap_flags[] __counted_by(flags_len);
+};
+
+VIRTCHNL_CHECK_STRUCT_LEN(4, virtchnl_vf_caps2);
+
 /**
  * struct virtchnl_phc_time - Contains the 64bits of PHC clock time in ns.
  * @time: PHC time in nanoseconds
@@ -1929,6 +1967,21 @@ virtchnl_vc_validate_vf_msg(struct virtchnl_version_info *ver, u32 v_opcode,
 	case VIRTCHNL_OP_1588_PTP_GET_TIME:
 		valid_len = sizeof(struct virtchnl_phc_time);
 		break;
+	case VIRTCHNL_OP_GET_VF_CAPS2: {
+		struct virtchnl_vf_caps2 *caps2 =
+			(struct virtchnl_vf_caps2 *)msg;
+
+		/* Require at least 1 element */
+		if (msglen < struct_size(caps2, vf_cap_flags, 1) ||
+		    msglen != struct_size(caps2, vf_cap_flags,
+					  caps2->flags_len)) {
+			err_msg_format = true;
+			break;
+		}
+
+		valid_len = struct_size(caps2, vf_cap_flags, caps2->flags_len);
+		}
+		break;
 	/* These are always errors coming from the VF. */
 	case VIRTCHNL_OP_EVENT:
 	case VIRTCHNL_OP_UNKNOWN:
diff --git a/drivers/net/ethernet/intel/i40e/i40e_virtchnl_pf.c b/drivers/net/ethernet/intel/i40e/i40e_virtchnl_pf.c
index 519f18dacd4a..9c233c3176c3 100644
--- a/drivers/net/ethernet/intel/i40e/i40e_virtchnl_pf.c
+++ b/drivers/net/ethernet/intel/i40e/i40e_virtchnl_pf.c
@@ -2149,6 +2149,11 @@ static int i40e_vc_get_vf_resources_msg(struct i40e_vf *vf, u8 *msg)
 
 	bitmap_zero(vf->driver_caps, VIRTCHNL_VF_CAPS_MAX);
 	if (VF_IS_V11(&vf->vf_ver)) {
+		/* VIRTCHNL_OP_GET_VF_RESOURCES only sends the first 32 flags.
+		 * If VIRTCHNL_VF_CAPS2 (bit 2) is set, then there are more
+		 * flags. A complete set (including the first 32) is then sent
+		 * via VIRTCHNL_OP_GET_VF_CAPS2.
+		 */
 		bitmap_from_arr32(vf->driver_caps, (u32 *)msg,
 				  BITS_PER_TYPE(u32));
 	} else {
@@ -2218,6 +2223,10 @@ static int i40e_vc_get_vf_resources_msg(struct i40e_vf *vf, u8 *msg)
 	if (test_bit(VIRTCHNL_VF_OFFLOAD_ADQ, vf->driver_caps))
 		vfres->vf_cap_flags |= BIT(VIRTCHNL_VF_OFFLOAD_ADQ);
 
+	/* Indicate that VF can send more flags in VIRTCHNL_OP_GET_VF_CAPS2 */
+	if (test_bit(VIRTCHNL_VF_CAPS2, vf->driver_caps))
+		vfres->vf_cap_flags |= BIT(VIRTCHNL_VF_CAPS2);
+
 	vfres->num_vsis = num_vsis;
 	vfres->num_queue_pairs = vf->num_queue_pairs;
 	vfres->max_vectors = pf->hw.func_caps.num_msix_vectors_vf;
@@ -4192,6 +4201,78 @@ static int i40e_vc_del_qch_msg(struct i40e_vf *vf, u8 *msg)
 				       aq_ret);
 }
 
+/**
+ * i40e_vc_get_vf_caps2 - parse extended capability flags
+ * @vf: pointer to the VF info
+ * @msg: pointer to the msg buffer
+ *
+ * Called from the VF to negotiate extended capability flag set if
+ * VIRTCHNL_VF_CAPS2 was set in vf_cap_flags. The PF responds with the
+ * intersection of its supported flags and the VF's requested flags.
+ *
+ * Return: 0 on success, negative on failure
+ **/
+static int i40e_vc_get_vf_caps2(struct i40e_vf *vf, u8 *msg)
+{
+	struct virtchnl_vf_caps2 *vf_msg = (struct virtchnl_vf_caps2 *)msg;
+	DECLARE_BITMAP(msg_caps, VIRTCHNL_VF_CAPS_MAX);
+	struct virtchnl_vf_caps2 *caps2;
+	unsigned int nbits;
+	int aq_ret = 0;
+	int err;
+
+	caps2 = kzalloc_flex(*caps2, vf_cap_flags,
+			     BITS_TO_U32(VIRTCHNL_VF_CAPS_MAX));
+	if (!caps2)
+		return -ENOMEM;
+
+	if (!test_bit(I40E_VF_STATE_ACTIVE, &vf->vf_states)) {
+		aq_ret = -EINVAL;
+		goto send_msg;
+	}
+
+	if (!test_bit(VIRTCHNL_VF_CAPS2, vf->driver_caps)) {
+		aq_ret = -EINVAL;
+		goto send_msg;
+	}
+
+	caps2->flags_len = BITS_TO_U32(VIRTCHNL_VF_CAPS_MAX);
+
+	/* Set extended feature flags. The first 32 bits should be already set
+	 * in VIRTCHNL_OP_GET_VF_RESOURCES
+	 */
+
+	/* Make sure to not read after msg bitmap or local bitmap. The remaining
+	 * bits (if any) should be unset, since one side doesn't know about
+	 * them, therefore cannot support these capabilities.
+	 */
+	nbits = min_t(unsigned int, VIRTCHNL_VF_CAPS_MAX,
+		      BITS_PER_TYPE(u32) * vf_msg->flags_len);
+	bitmap_zero(msg_caps, VIRTCHNL_VF_CAPS_MAX);
+	bitmap_from_arr32(msg_caps, vf_msg->vf_cap_flags, nbits);
+
+	/* Note that msg->vf_cap_flags cannot be directly used, because then
+	 * we'd need to limit the operation range to nbits. If the local caps
+	 * is larger and has some flags set after nbits, then they would be
+	 * incorrectly left set.
+	 */
+	bitmap_and(vf->driver_caps, vf->driver_caps, msg_caps,
+		   VIRTCHNL_VF_CAPS_MAX);
+
+	bitmap_to_arr32(caps2->vf_cap_flags, vf->driver_caps,
+			VIRTCHNL_VF_CAPS_MAX);
+
+send_msg:
+	err = i40e_vc_send_msg_to_vf(vf, VIRTCHNL_OP_GET_VF_CAPS2, aq_ret,
+				     (u8 *)caps2,
+				     struct_size(caps2, vf_cap_flags,
+						 caps2->flags_len));
+
+	kfree(caps2);
+
+	return err;
+}
+
 /**
  * i40e_vc_process_vf_msg
  * @pf: pointer to the PF structure
@@ -4316,6 +4397,9 @@ int i40e_vc_process_vf_msg(struct i40e_pf *pf, s16 vf_id, u32 v_opcode,
 	case VIRTCHNL_OP_DEL_CLOUD_FILTER:
 		ret = i40e_vc_del_cloud_filter(vf, msg);
 		break;
+	case VIRTCHNL_OP_GET_VF_CAPS2:
+		ret = i40e_vc_get_vf_caps2(vf, msg);
+		break;
 	case VIRTCHNL_OP_UNKNOWN:
 	default:
 		dev_err(&pf->pdev->dev, "Unsupported opcode %d from VF %d\n",
diff --git a/drivers/net/ethernet/intel/iavf/iavf_main.c b/drivers/net/ethernet/intel/iavf/iavf_main.c
index ce2b61e56f9d..bb9d4b8c4b9b 100644
--- a/drivers/net/ethernet/intel/iavf/iavf_main.c
+++ b/drivers/net/ethernet/intel/iavf/iavf_main.c
@@ -2038,6 +2038,8 @@ static int iavf_process_aq_command(struct iavf_adapter *adapter)
 		return iavf_send_vf_supported_rxdids_msg(adapter);
 	if (adapter->aq_required & IAVF_FLAG_AQ_GET_PTP_CAPS)
 		return iavf_send_vf_ptp_caps_msg(adapter);
+	if (adapter->aq_required & IAVF_FLAG_AQ_GET_VF_CAP_CAPS2)
+		return iavf_send_vf_caps2_msg(adapter);
 	if (adapter->aq_required & IAVF_FLAG_AQ_DISABLE_QUEUES) {
 		iavf_disable_queues(adapter);
 		return 0;
@@ -2675,6 +2677,55 @@ static void iavf_init_recv_ptp_caps(struct iavf_adapter *adapter)
 	iavf_change_state(adapter, __IAVF_INIT_FAILED);
 }
 
+/**
+ * iavf_init_send_flags2_caps - part of negotiating CAPS2 capabilities
+ * @adapter: board private structure
+ *
+ * Send VIRTCHNL_OP_GET_VF_CAPS2 message to the PF. Clear
+ * IAVF_EXTENDED_CAP_RECV_CAPS2 if the message is not sent, e.g. due to PF not
+ * negotiating VIRTCHNL_VF_CAPS2.
+ */
+static void iavf_init_send_flags2_caps(struct iavf_adapter *adapter)
+{
+	WARN_ON(!(adapter->extended_caps & IAVF_EXTENDED_CAP_SEND_CAPS2));
+
+	if (iavf_send_vf_caps2_msg(adapter) == -EOPNOTSUPP) {
+		/* PF does not support VIRTCHNL_VF_CAPS2. In this case, we
+		 * did not send the capability exchange message and do not
+		 * expect a response.
+		 */
+		adapter->extended_caps &= ~IAVF_EXTENDED_CAP_RECV_CAPS2;
+	}
+
+	/* We sent the message, so move on to the next step */
+	adapter->extended_caps &= ~IAVF_EXTENDED_CAP_SEND_CAPS2;
+}
+
+/**
+ * iavf_init_recv_flags2_caps - part of negotiating CAPS2 capabilities
+ * @adapter: board private structure
+ *
+ * Process receipt of VIRTCHNL_VF_CAPS2 message from PF.
+ */
+static void iavf_init_recv_flags2_caps(struct iavf_adapter *adapter)
+{
+	WARN_ON(!(adapter->extended_caps & IAVF_EXTENDED_CAP_RECV_CAPS2));
+
+	if (iavf_get_vf_caps2(adapter))
+		goto err;
+
+	/* We've processed the PF response to VIRTCHNL_OP_GET_VF_CAPS2 */
+	adapter->extended_caps &= ~IAVF_EXTENDED_CAP_RECV_CAPS2;
+	return;
+
+err:
+	/* We didn't receive a reply. Make sure we try sending again when
+	 * __IAVF_INIT_FAILED attempts to recover.
+	 */
+	adapter->extended_caps |= IAVF_EXTENDED_CAP_SEND_CAPS2;
+	iavf_change_state(adapter, __IAVF_INIT_FAILED);
+}
+
 /**
  * iavf_init_process_extended_caps - Part of driver startup
  * @adapter: board private structure
@@ -2717,6 +2768,15 @@ static void iavf_init_process_extended_caps(struct iavf_adapter *adapter)
 		return;
 	}
 
+	/* Process capability exchange for CAPS2 */
+	if (adapter->extended_caps & IAVF_EXTENDED_CAP_SEND_CAPS2) {
+		iavf_init_send_flags2_caps(adapter);
+		return;
+	} else if (adapter->extended_caps & IAVF_EXTENDED_CAP_RECV_CAPS2) {
+		iavf_init_recv_flags2_caps(adapter);
+		return;
+	}
+
 	/* When we reach here, no further extended capabilities exchanges are
 	 * necessary, so we finally transition into __IAVF_INIT_CONFIG_ADAPTER
 	 */
diff --git a/drivers/net/ethernet/intel/iavf/iavf_virtchnl.c b/drivers/net/ethernet/intel/iavf/iavf_virtchnl.c
index 38be4caf77f0..4509a1501928 100644
--- a/drivers/net/ethernet/intel/iavf/iavf_virtchnl.c
+++ b/drivers/net/ethernet/intel/iavf/iavf_virtchnl.c
@@ -172,7 +172,8 @@ int iavf_send_vf_config_msg(struct iavf_adapter *adapter)
 	       BIT(VIRTCHNL_VF_OFFLOAD_FDIR_PF) |
 	       BIT(VIRTCHNL_VF_OFFLOAD_ADV_RSS_PF) |
 	       BIT(VIRTCHNL_VF_CAP_ADV_LINK_SPEED) |
-	       BIT(VIRTCHNL_VF_OFFLOAD_QOS);
+	       BIT(VIRTCHNL_VF_OFFLOAD_QOS) |
+	       BIT(VIRTCHNL_VF_CAPS2);
 
 	adapter->current_op = VIRTCHNL_OP_GET_VF_RESOURCES;
 	adapter->aq_required &= ~IAVF_FLAG_AQ_GET_CONFIG;
@@ -245,6 +246,52 @@ int iavf_send_vf_ptp_caps_msg(struct iavf_adapter *adapter)
 				(u8 *)&hw_caps, sizeof(hw_caps));
 }
 
+/**
+ * iavf_send_vf_caps2_msg - send request for second capability flags set
+ * @adapter: adapter structure
+ *
+ * Send VIRTCHNL_OP_GET_VF_CAPS2 to the PF with the VF's desired flags2
+ * bitmap. The PF responds with the intersection of the flags it supports.
+ *
+ * Return: 0 on success, -EOPNOTSUPP if CAPS2 was not negotiated, other error
+ * code on failure
+ */
+int iavf_send_vf_caps2_msg(struct iavf_adapter *adapter)
+{
+	DECLARE_BITMAP(msg_caps, VIRTCHNL_VF_CAPS_MAX);
+	struct virtchnl_vf_caps2 *caps2;
+	int err;
+
+	adapter->aq_required &= ~IAVF_FLAG_AQ_GET_VF_CAP_CAPS2;
+
+	if (!IAVF_CAPS2_ALLOWED(adapter))
+		return -EOPNOTSUPP;
+
+	caps2 = kzalloc_flex(*caps2, vf_cap_flags,
+			     BITS_TO_U32(VIRTCHNL_VF_CAPS_MAX));
+	if (!caps2)
+		return -ENOMEM;
+
+	caps2->flags_len = BITS_TO_U32(VIRTCHNL_VF_CAPS_MAX);
+
+	/* Copy from the local bitmap (should have only first 32 bits set) and
+	 * set extended feature flags to send.
+	 */
+	bitmap_copy(msg_caps, adapter->vf_cap_flags, VIRTCHNL_VF_CAPS_MAX);
+
+	bitmap_to_arr32(caps2->vf_cap_flags, msg_caps, VIRTCHNL_VF_CAPS_MAX);
+
+	adapter->current_op = VIRTCHNL_OP_GET_VF_CAPS2;
+
+	err = iavf_send_pf_msg(adapter, VIRTCHNL_OP_GET_VF_CAPS2, (u8 *)caps2,
+			       struct_size(caps2, vf_cap_flags,
+					   caps2->flags_len));
+
+	kfree(caps2);
+
+	return err;
+}
+
 /**
  * iavf_validate_num_queues
  * @adapter: adapter structure
@@ -296,7 +343,7 @@ int iavf_get_vf_config(struct iavf_adapter *adapter)
 	err = iavf_poll_virtchnl_msg(hw, &event, VIRTCHNL_OP_GET_VF_RESOURCES);
 	memcpy(adapter->vf_res, event.msg_buf, min(event.msg_len, len));
 
-	/* mirror to the extended bitmap */
+	/* mirror the first 32 bits to the extended bitmap */
 	bitmap_from_arr32(adapter->vf_cap_flags, &adapter->vf_res->vf_cap_flags,
 			  BITS_PER_TYPE(u32));
 
@@ -369,6 +416,78 @@ int iavf_get_vf_ptp_caps(struct iavf_adapter *adapter)
 	return err;
 }
 
+/**
+ * iavf_process_caps2 - store received extended caps in adapter
+ * @adapter: adapter structure
+ * @caps2: received extended capabilities message
+ *
+ * Return: 0 on success, negative on failure
+ */
+static int iavf_process_caps2(struct iavf_adapter *adapter,
+			      struct virtchnl_vf_caps2 *caps2)
+{
+	unsigned int nbits;
+
+	if (caps2->flags_len == 0)
+		return -EINVAL;
+
+	/* Make sure to not read after msg bitmap or local bitmap. The remaining
+	 * bits (if any) should be unset, since one side doesn't know about
+	 * them, therefore cannot support these capabilities.
+	 */
+	nbits = min_t(unsigned int, VIRTCHNL_VF_CAPS_MAX,
+		      BITS_PER_TYPE(u32) * caps2->flags_len);
+
+	bitmap_zero(adapter->vf_cap_flags, VIRTCHNL_VF_CAPS_MAX);
+	bitmap_from_arr32(adapter->vf_cap_flags, caps2->vf_cap_flags, nbits);
+	/* Assume the first 32 bits were already written to
+	 * adapter->vf_res->vf_cap_flags in VIRTCHNL_OP_GET_VF_RESOURCES. Driver
+	 * should not use these anyway.
+	 */
+
+	return 0;
+}
+
+/**
+ * iavf_get_vf_caps2 - receive second capability flags from PF
+ * @adapter: adapter structure
+ *
+ * Poll the admin queue for a response to VIRTCHNL_OP_GET_VF_CAPS2 and process
+ * the negotiated flags.
+ *
+ * Return: 0 on success, negative on failure
+ */
+int iavf_get_vf_caps2(struct iavf_adapter *adapter)
+{
+	struct iavf_arq_event_info event;
+	struct virtchnl_vf_caps2 *caps2;
+	int err;
+
+	/* Actual length might be larger if PF knows more flags, but we don't
+	 * care about them since they should be unset after the negotiation.
+	 * Even if they were somehow not, VF has no way to store them.
+	 */
+	event.buf_len = struct_size(caps2, vf_cap_flags, VIRTCHNL_VF_CAPS_MAX);
+
+	event.msg_buf = kzalloc(event.buf_len, GFP_KERNEL);
+	if (!event.msg_buf)
+		return -ENOMEM;
+
+	err = iavf_poll_virtchnl_msg(&adapter->hw, &event,
+				     VIRTCHNL_OP_GET_VF_CAPS2);
+	if (err)
+		goto free_msg_buf;
+
+	caps2 = (struct virtchnl_vf_caps2 *)event.msg_buf;
+
+	err = iavf_process_caps2(adapter, caps2);
+
+free_msg_buf:
+	kfree(event.msg_buf);
+
+	return err;
+}
+
 /**
  * iavf_configure_queues
  * @adapter: adapter structure
@@ -2707,6 +2826,21 @@ void iavf_virtchnl_completion(struct iavf_adapter *adapter,
 	case VIRTCHNL_OP_1588_PTP_GET_TIME:
 		iavf_virtchnl_ptp_get_time(adapter, msg, msglen);
 		break;
+	case VIRTCHNL_OP_GET_VF_CAPS2: {
+		struct virtchnl_vf_caps2 *caps2 =
+			(struct virtchnl_vf_caps2 *)msg;
+
+		/* Require at least 1 element */
+		if (msglen < struct_size(caps2, vf_cap_flags, 1) ||
+		    msglen != struct_size(caps2, vf_cap_flags,
+					  caps2->flags_len))
+			return;
+
+		if (iavf_process_caps2(adapter, caps2))
+			dev_warn(&adapter->pdev->dev,
+				 "Invalid extended flags received from PF\n");
+		}
+		break;
 	case VIRTCHNL_OP_ENABLE_QUEUES:
 		/* enable transmits */
 		iavf_irq_enable(adapter, true);
diff --git a/drivers/net/ethernet/intel/ice/virt/allowlist.c b/drivers/net/ethernet/intel/ice/virt/allowlist.c
index eb3d3b4e65a9..a5c27489a7ff 100644
--- a/drivers/net/ethernet/intel/ice/virt/allowlist.c
+++ b/drivers/net/ethernet/intel/ice/virt/allowlist.c
@@ -90,6 +90,11 @@ static const u32 ptp_allowlist_opcodes[] = {
 	VIRTCHNL_OP_1588_PTP_GET_TIME,
 };
 
+/* VIRTCHNL_VF_CAPS2 */
+static const u32 caps2_allowlist_opcodes[] = {
+	VIRTCHNL_OP_GET_VF_CAPS2,
+};
+
 static const u32 tc_allowlist_opcodes[] = {
 	VIRTCHNL_OP_GET_QOS_CAPS, VIRTCHNL_OP_CONFIG_QUEUE_BW,
 	VIRTCHNL_OP_CONFIG_QUANTA,
@@ -117,6 +122,7 @@ static const struct allowlist_opcode_info allowlist_opcodes[] = {
 	ALLOW_ITEM(VIRTCHNL_VF_OFFLOAD_VLAN_V2, vlan_v2_allowlist_opcodes),
 	ALLOW_ITEM(VIRTCHNL_VF_OFFLOAD_QOS, tc_allowlist_opcodes),
 	ALLOW_ITEM(VIRTCHNL_VF_CAP_PTP, ptp_allowlist_opcodes),
+	ALLOW_ITEM(VIRTCHNL_VF_CAPS2, caps2_allowlist_opcodes),
 };
 
 /**
diff --git a/drivers/net/ethernet/intel/ice/virt/virtchnl.c b/drivers/net/ethernet/intel/ice/virt/virtchnl.c
index f15e8c82d52d..37bf02cd4d41 100644
--- a/drivers/net/ethernet/intel/ice/virt/virtchnl.c
+++ b/drivers/net/ethernet/intel/ice/virt/virtchnl.c
@@ -267,6 +267,11 @@ static int ice_vc_get_vf_res_msg(struct ice_vf *vf, u8 *msg)
 
 	bitmap_zero(vf->driver_caps, VIRTCHNL_VF_CAPS_MAX);
 	if (VF_IS_V11(&vf->vf_ver)) {
+		/* VIRTCHNL_OP_GET_VF_RESOURCES only sends the first 32 flags.
+		 * If VIRTCHNL_VF_CAPS2 (bit 2) is set, then there are more
+		 * flags. A complete set (including the first 32) is then sent
+		 * via VIRTCHNL_OP_GET_VF_CAPS2.
+		 */
 		bitmap_from_arr32(vf->driver_caps, (u32 *)msg,
 				  BITS_PER_TYPE(u32));
 	} else {
@@ -333,6 +338,10 @@ static int ice_vc_get_vf_res_msg(struct ice_vf *vf, u8 *msg)
 	if (test_bit(VIRTCHNL_VF_CAP_PTP, vf->driver_caps))
 		vfres->vf_cap_flags |= BIT(VIRTCHNL_VF_CAP_PTP);
 
+	/* Indicate that VF can send more flags in VIRTCHNL_OP_GET_VF_CAPS2 */
+	if (test_bit(VIRTCHNL_VF_CAPS2, vf->driver_caps))
+		vfres->vf_cap_flags |= BIT(VIRTCHNL_VF_CAPS2);
+
 	vfres->num_vsis = 1;
 	/* Tx and Rx queue are equal for VF */
 	vfres->num_queue_pairs = vsi->num_txq;
@@ -2453,6 +2462,78 @@ static int ice_vc_dis_vlan_insertion_v2_msg(struct ice_vf *vf, u8 *msg)
 				     v_ret, NULL, 0);
 }
 
+/**
+ * ice_vc_get_vf_caps2 - handle VIRTCHNL_OP_GET_VF_CAPS2
+ * @vf: pointer to VF
+ * @msg: pointer to the message buffer
+ *
+ * Called from the VF to negotiate extended capability flag set if
+ * VIRTCHNL_VF_CAPS2 was set in vf_cap_flags. The PF responds with the
+ * intersection of its supported flags and the VF's requested flags.
+ *
+ * Return: 0 on success, negative on failure
+ */
+static int ice_vc_get_vf_caps2(struct ice_vf *vf,
+			       const struct virtchnl_vf_caps2 *msg)
+{
+	enum virtchnl_status_code v_ret = VIRTCHNL_STATUS_SUCCESS;
+	DECLARE_BITMAP(msg_caps, VIRTCHNL_VF_CAPS_MAX);
+	struct virtchnl_vf_caps2 *caps2;
+	unsigned int nbits;
+	int err;
+
+	caps2 = kzalloc_flex(*caps2, vf_cap_flags,
+			     BITS_TO_U32(VIRTCHNL_VF_CAPS_MAX));
+	if (!caps2)
+		return -ENOMEM;
+
+	if (!test_bit(ICE_VF_STATE_ACTIVE, vf->vf_states)) {
+		v_ret = VIRTCHNL_STATUS_ERR_PARAM;
+		goto send_msg;
+	}
+
+	if (!test_bit(VIRTCHNL_VF_CAPS2, vf->driver_caps)) {
+		v_ret = VIRTCHNL_STATUS_ERR_PARAM;
+		goto send_msg;
+	}
+
+	caps2->flags_len = BITS_TO_U32(VIRTCHNL_VF_CAPS_MAX);
+
+	/* Set extended feature flags. The first 32 bits should be already set
+	 * in VIRTCHNL_OP_GET_VF_RESOURCES
+	 */
+
+	/* Make sure to not read after msg bitmap or local bitmap. The remaining
+	 * bits (if any) should be unset, since one side doesn't know about
+	 * them, therefore cannot support these capabilities.
+	 */
+	nbits = min_t(unsigned int, VIRTCHNL_VF_CAPS_MAX,
+		      BITS_PER_TYPE(u32) * msg->flags_len);
+	bitmap_zero(msg_caps, VIRTCHNL_VF_CAPS_MAX);
+	bitmap_from_arr32(msg_caps, msg->vf_cap_flags, nbits);
+
+	/* Note that msg->vf_cap_flags cannot be directly used, because then
+	 * we'd need to limit the operation range to nbits. If the local caps
+	 * is larger and has some flags set after nbits, then they would be
+	 * incorrectly left set.
+	 */
+	bitmap_and(vf->driver_caps, vf->driver_caps, msg_caps,
+		   VIRTCHNL_VF_CAPS_MAX);
+
+	bitmap_to_arr32(caps2->vf_cap_flags, vf->driver_caps,
+			VIRTCHNL_VF_CAPS_MAX);
+
+send_msg:
+	err = ice_vc_send_msg_to_vf(vf, VIRTCHNL_OP_GET_VF_CAPS2, v_ret,
+				    (u8 *)caps2,
+				    struct_size(caps2, vf_cap_flags,
+						caps2->flags_len));
+
+	kfree(caps2);
+
+	return err;
+}
+
 static int ice_vc_get_ptp_cap(struct ice_vf *vf,
 			      const struct virtchnl_ptp_caps *msg)
 {
@@ -2544,6 +2625,7 @@ static const struct ice_virtchnl_ops ice_virtchnl_dflt_ops = {
 	.cfg_q_quanta = ice_vc_cfg_q_quanta,
 	.get_ptp_cap = ice_vc_get_ptp_cap,
 	.get_phc_time = ice_vc_get_phc_time,
+	.get_vf_caps2 = ice_vc_get_vf_caps2,
 	/* If you add a new op here please make sure to add it to
 	 * ice_virtchnl_repr_ops as well.
 	 */
@@ -2681,6 +2763,7 @@ static const struct ice_virtchnl_ops ice_virtchnl_repr_ops = {
 	.cfg_q_quanta = ice_vc_cfg_q_quanta,
 	.get_ptp_cap = ice_vc_get_ptp_cap,
 	.get_phc_time = ice_vc_get_phc_time,
+	.get_vf_caps2 = ice_vc_get_vf_caps2,
 };
 
 /**
@@ -2924,6 +3007,9 @@ void ice_vc_process_vf_msg(struct ice_pf *pf, struct ice_rq_event_info *event,
 	case VIRTCHNL_OP_1588_PTP_GET_TIME:
 		err = ops->get_phc_time(vf);
 		break;
+	case VIRTCHNL_OP_GET_VF_CAPS2:
+		err = ops->get_vf_caps2(vf, (const void *)msg);
+		break;
 	case VIRTCHNL_OP_UNKNOWN:
 	default:
 		dev_err(dev, "Unsupported opcode %d from VF %d\n", v_opcode,
-- 
2.49.0


^ permalink raw reply related

* [PATCH iwl-next 2/3] virtchnl, iavf, ice, i40e: store VIRTCHNL_VF_* flags in bitmaps
From: Marcin Szycik @ 2026-04-28 14:37 UTC (permalink / raw)
  To: intel-wired-lan
  Cc: netdev, paul.greenwalt, jacob.e.keller, Marcin Szycik,
	Przemek Kitszel, Aleksandr Loktionov
In-Reply-To: <20260428143716.9653-1-marcin.szycik@linux.intel.com>

As part of preparation to add extended (32nd and more) capability flags,
change all flags from bitmasks to bit numbers and store them in bitmaps
instead of u32. Change all bitops to test_bit()/set_bit()/etc.

Functionally nothing is changed, except that iavf now stores capability
flags in struct iavf_adapter::vf_cap_flags instead of
iavf_adapter::vf_res::vf_cap_flags to allow it to grow in the future.

Signed-off-by: Marcin Szycik <marcin.szycik@linux.intel.com>
Reviewed-by: Przemek Kitszel <przemyslaw.kitszel@intel.com>
Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
Reviewed-by: Jacob Keller <jacob.e.keller@intel.com>
---
 .../ethernet/intel/i40e/i40e_virtchnl_pf.h    |   4 +-
 drivers/net/ethernet/intel/iavf/iavf.h        |  51 ++++----
 .../net/ethernet/intel/iavf/iavf_prototype.h  |   3 +-
 drivers/net/ethernet/intel/ice/ice_vf_lib.h   |   5 +-
 include/linux/intel/virtchnl.h                |  62 +++++-----
 .../ethernet/intel/i40e/i40e_virtchnl_pf.c    |  76 ++++++------
 drivers/net/ethernet/intel/iavf/iavf_common.c |   7 +-
 .../net/ethernet/intel/iavf/iavf_ethtool.c    |   2 +-
 drivers/net/ethernet/intel/iavf/iavf_main.c   |  36 +++---
 .../net/ethernet/intel/iavf/iavf_virtchnl.c   |  51 ++++----
 drivers/net/ethernet/intel/ice/ice_vf_lib.c   |   4 +-
 .../net/ethernet/intel/ice/virt/allowlist.c   |   5 +-
 drivers/net/ethernet/intel/ice/virt/fdir.c    |   2 +-
 drivers/net/ethernet/intel/ice/virt/queues.c  |  13 ++-
 drivers/net/ethernet/intel/ice/virt/rss.c     |   4 +-
 .../net/ethernet/intel/ice/virt/virtchnl.c    | 109 +++++++++---------
 16 files changed, 227 insertions(+), 207 deletions(-)

diff --git a/drivers/net/ethernet/intel/i40e/i40e_virtchnl_pf.h b/drivers/net/ethernet/intel/i40e/i40e_virtchnl_pf.h
index a03ecddfb956..b2b50c1fccf1 100644
--- a/drivers/net/ethernet/intel/i40e/i40e_virtchnl_pf.h
+++ b/drivers/net/ethernet/intel/i40e/i40e_virtchnl_pf.h
@@ -80,7 +80,9 @@ struct i40e_vf {
 	/* all VF vsis connect to the same parent */
 	enum i40e_switch_element_types parent_type;
 	struct virtchnl_version_info vf_ver;
-	u32 driver_caps; /* reported by VF driver */
+
+	/* reported by VF driver */
+	DECLARE_BITMAP(driver_caps, VIRTCHNL_VF_CAPS_MAX);
 
 	/* VF Port Extender (PE) stag if used */
 	u16 stag;
diff --git a/drivers/net/ethernet/intel/iavf/iavf.h b/drivers/net/ethernet/intel/iavf/iavf.h
index 1a1a66b3311e..64576cba3a01 100644
--- a/drivers/net/ethernet/intel/iavf/iavf.h
+++ b/drivers/net/ethernet/intel/iavf/iavf.h
@@ -395,7 +395,7 @@ struct iavf_adapter {
 	bool link_up;
 	enum virtchnl_link_speed link_speed;
 	/* This is only populated if the VIRTCHNL_VF_CAP_ADV_LINK_SPEED is set
-	 * in vf_res->vf_cap_flags. Use ADV_LINK_SUPPORT macro to determine if
+	 * in vf_cap_flags. Use ADV_LINK_SUPPORT macro to determine if
 	 * this field is valid. This field should be used going forward and the
 	 * enum virtchnl_link_speed above should be considered the legacy way of
 	 * storing/communicating link speeds.
@@ -404,39 +404,32 @@ struct iavf_adapter {
 
 	enum virtchnl_ops current_op;
 /* RSS by the PF should be preferred over RSS via other methods. */
-#define RSS_PF(_a) ((_a)->vf_res->vf_cap_flags & \
-		    VIRTCHNL_VF_OFFLOAD_RSS_PF)
-#define RSS_AQ(_a) ((_a)->vf_res->vf_cap_flags & \
-		    VIRTCHNL_VF_OFFLOAD_RSS_AQ)
-#define RSS_REG(_a) (!((_a)->vf_res->vf_cap_flags & \
-		       (VIRTCHNL_VF_OFFLOAD_RSS_AQ | \
-			VIRTCHNL_VF_OFFLOAD_RSS_PF)))
-#define VLAN_ALLOWED(_a) ((_a)->vf_res->vf_cap_flags & \
-			  VIRTCHNL_VF_OFFLOAD_VLAN)
-#define VLAN_V2_ALLOWED(_a) ((_a)->vf_res->vf_cap_flags & \
-			     VIRTCHNL_VF_OFFLOAD_VLAN_V2)
-#define CRC_OFFLOAD_ALLOWED(_a) ((_a)->vf_res->vf_cap_flags & \
-				 VIRTCHNL_VF_OFFLOAD_CRC)
-#define TC_U32_SUPPORT(_a) ((_a)->vf_res->vf_cap_flags & \
-			    VIRTCHNL_VF_OFFLOAD_TC_U32)
+#define RSS_PF(_a) test_bit(VIRTCHNL_VF_OFFLOAD_RSS_PF, (_a)->vf_cap_flags)
+#define RSS_AQ(_a) test_bit(VIRTCHNL_VF_OFFLOAD_RSS_AQ, (_a)->vf_cap_flags)
+#define RSS_REG(_a) (!(RSS_PF(_a) || RSS_AQ(_a)))
+#define VLAN_ALLOWED(_a) test_bit(VIRTCHNL_VF_OFFLOAD_VLAN, (_a)->vf_cap_flags)
+#define VLAN_V2_ALLOWED(_a)						\
+	test_bit(VIRTCHNL_VF_OFFLOAD_VLAN_V2, (_a)->vf_cap_flags)
+#define CRC_OFFLOAD_ALLOWED(_a)						\
+	test_bit(VIRTCHNL_VF_OFFLOAD_CRC, (_a)->vf_cap_flags)
+#define TC_U32_SUPPORT(_a)						\
+	test_bit(VIRTCHNL_VF_OFFLOAD_TC_U32, (_a)->vf_cap_flags)
 #define VLAN_V2_FILTERING_ALLOWED(_a) \
 	(VLAN_V2_ALLOWED((_a)) && \
 	 ((_a)->vlan_v2_caps.filtering.filtering_support.outer || \
 	  (_a)->vlan_v2_caps.filtering.filtering_support.inner))
 #define VLAN_FILTERING_ALLOWED(_a) \
 	(VLAN_ALLOWED((_a)) || VLAN_V2_FILTERING_ALLOWED((_a)))
-#define ADV_LINK_SUPPORT(_a) ((_a)->vf_res->vf_cap_flags & \
-			      VIRTCHNL_VF_CAP_ADV_LINK_SPEED)
-#define FDIR_FLTR_SUPPORT(_a) ((_a)->vf_res->vf_cap_flags & \
-			       VIRTCHNL_VF_OFFLOAD_FDIR_PF)
-#define ADV_RSS_SUPPORT(_a) ((_a)->vf_res->vf_cap_flags & \
-			     VIRTCHNL_VF_OFFLOAD_ADV_RSS_PF)
-#define QOS_ALLOWED(_a) ((_a)->vf_res->vf_cap_flags & \
-			 VIRTCHNL_VF_OFFLOAD_QOS)
+#define ADV_LINK_SUPPORT(_a)						\
+	test_bit(VIRTCHNL_VF_CAP_ADV_LINK_SPEED, (_a)->vf_cap_flags)
+#define FDIR_FLTR_SUPPORT(_a)						\
+	test_bit(VIRTCHNL_VF_OFFLOAD_FDIR_PF, (_a)->vf_cap_flags)
+#define ADV_RSS_SUPPORT(_a)						\
+	test_bit(VIRTCHNL_VF_OFFLOAD_ADV_RSS_PF, (_a)->vf_cap_flags)
+#define QOS_ALLOWED(_a) test_bit(VIRTCHNL_VF_OFFLOAD_QOS, (_a)->vf_cap_flags)
 #define IAVF_RXDID_ALLOWED(a)						\
-	((a)->vf_res->vf_cap_flags & VIRTCHNL_VF_OFFLOAD_RX_FLEX_DESC)
-#define IAVF_PTP_ALLOWED(a)						\
-	((a)->vf_res->vf_cap_flags & VIRTCHNL_VF_CAP_PTP)
+	test_bit(VIRTCHNL_VF_OFFLOAD_RX_FLEX_DESC, (a)->vf_cap_flags)
+#define IAVF_PTP_ALLOWED(a) test_bit(VIRTCHNL_VF_CAP_PTP, (a)->vf_cap_flags)
 	struct virtchnl_vf_resource *vf_res; /* incl. all VSIs */
 	struct virtchnl_vsi_resource *vsi_res; /* our LAN VSI */
 	struct virtchnl_version_info pf_version;
@@ -445,6 +438,10 @@ struct iavf_adapter {
 	struct virtchnl_vlan_caps vlan_v2_caps;
 	u64 supp_rxdids;
 	struct iavf_ptp ptp;
+
+	/* Mirrors vf_res->vf_cap_flags */
+	DECLARE_BITMAP(vf_cap_flags, VIRTCHNL_VF_CAPS_MAX);
+
 	u16 msg_enable;
 	struct iavf_eth_stats current_stats;
 	struct virtchnl_qos_cap_list *qos_caps;
diff --git a/drivers/net/ethernet/intel/iavf/iavf_prototype.h b/drivers/net/ethernet/intel/iavf/iavf_prototype.h
index a3348b063723..985111b152d5 100644
--- a/drivers/net/ethernet/intel/iavf/iavf_prototype.h
+++ b/drivers/net/ethernet/intel/iavf/iavf_prototype.h
@@ -42,7 +42,8 @@ enum iavf_status iavf_aq_set_rss_key(struct iavf_hw *hw, u16 seid,
 				     struct iavf_aqc_get_set_rss_key_data *key);
 
 void iavf_vf_parse_hw_config(struct iavf_hw *hw,
-			     struct virtchnl_vf_resource *msg);
+			     struct virtchnl_vf_resource *msg,
+			     const unsigned long *vf_cap_flags);
 enum iavf_status iavf_aq_send_msg_to_pf(struct iavf_hw *hw,
 					enum virtchnl_ops v_opcode,
 					enum iavf_status v_retval,
diff --git a/drivers/net/ethernet/intel/ice/ice_vf_lib.h b/drivers/net/ethernet/intel/ice/ice_vf_lib.h
index c520e22e3d0a..d44c060d3c91 100644
--- a/drivers/net/ethernet/intel/ice/ice_vf_lib.h
+++ b/drivers/net/ethernet/intel/ice/ice_vf_lib.h
@@ -157,7 +157,10 @@ struct ice_vf {
 	u64 rss_hashcfg;		/* RSS hash configuration */
 	struct ice_sw *vf_sw_id;	/* switch ID the VF VSIs connect to */
 	struct virtchnl_version_info vf_ver;
-	u32 driver_caps;		/* reported by VF driver */
+
+	/* reported by VF driver */
+	DECLARE_BITMAP(driver_caps, VIRTCHNL_VF_CAPS_MAX);
+
 	u8 dev_lan_addr[ETH_ALEN];
 	u8 hw_lan_addr[ETH_ALEN];
 	struct ice_time_mac legacy_last_added_umac;
diff --git a/include/linux/intel/virtchnl.h b/include/linux/intel/virtchnl.h
index bb4737de37fe..577b0549b437 100644
--- a/include/linux/intel/virtchnl.h
+++ b/include/linux/intel/virtchnl.h
@@ -244,35 +244,39 @@ struct virtchnl_vsi_resource {
 
 VIRTCHNL_CHECK_STRUCT_LEN(16, virtchnl_vsi_resource);
 
-/* VF capability flags
- * VIRTCHNL_VF_OFFLOAD_L2 flag is inclusive of base mode L2 offloads including
- * TX/RX Checksum offloading and TSO for non-tunnelled packets.
- */
-#define VIRTCHNL_VF_OFFLOAD_L2			BIT(0)
-#define VIRTCHNL_VF_OFFLOAD_RDMA		BIT(1)
-#define VIRTCHNL_VF_OFFLOAD_RSS_AQ		BIT(3)
-#define VIRTCHNL_VF_OFFLOAD_RSS_REG		BIT(4)
-#define VIRTCHNL_VF_OFFLOAD_WB_ON_ITR		BIT(5)
-#define VIRTCHNL_VF_OFFLOAD_REQ_QUEUES		BIT(6)
-/* used to negotiate communicating link speeds in Mbps */
-#define VIRTCHNL_VF_CAP_ADV_LINK_SPEED		BIT(7)
-#define  VIRTCHNL_VF_OFFLOAD_CRC		BIT(10)
-#define VIRTCHNL_VF_OFFLOAD_TC_U32		BIT(11)
-#define VIRTCHNL_VF_OFFLOAD_VLAN_V2		BIT(15)
-#define VIRTCHNL_VF_OFFLOAD_VLAN		BIT(16)
-#define VIRTCHNL_VF_OFFLOAD_RX_POLLING		BIT(17)
-#define VIRTCHNL_VF_OFFLOAD_RSS_PCTYPE_V2	BIT(18)
-#define VIRTCHNL_VF_OFFLOAD_RSS_PF		BIT(19)
-#define VIRTCHNL_VF_OFFLOAD_ENCAP		BIT(20)
-#define VIRTCHNL_VF_OFFLOAD_ENCAP_CSUM		BIT(21)
-#define VIRTCHNL_VF_OFFLOAD_RX_ENCAP_CSUM	BIT(22)
-#define VIRTCHNL_VF_OFFLOAD_ADQ			BIT(23)
-#define VIRTCHNL_VF_OFFLOAD_USO			BIT(25)
-#define VIRTCHNL_VF_OFFLOAD_RX_FLEX_DESC	BIT(26)
-#define VIRTCHNL_VF_OFFLOAD_ADV_RSS_PF		BIT(27)
-#define VIRTCHNL_VF_OFFLOAD_FDIR_PF		BIT(28)
-#define VIRTCHNL_VF_OFFLOAD_QOS			BIT(29)
-#define VIRTCHNL_VF_CAP_PTP			BIT(31)
+/* VF capability flags */
+enum virtchnl_vf_caps_bitnr {
+	/* Inclusive of base mode L2 offloads including TX/RX Checksum
+	 * offloading and TSO for non-tunnelled packets.
+	 */
+	VIRTCHNL_VF_OFFLOAD_L2 = 0,
+	VIRTCHNL_VF_OFFLOAD_RDMA = 1,
+	VIRTCHNL_VF_OFFLOAD_RSS_AQ = 3,
+	VIRTCHNL_VF_OFFLOAD_RSS_REG = 4,
+	VIRTCHNL_VF_OFFLOAD_WB_ON_ITR = 5,
+	VIRTCHNL_VF_OFFLOAD_REQ_QUEUES = 6,
+	/* Used to negotiate communicating link speeds in Mbps */
+	VIRTCHNL_VF_CAP_ADV_LINK_SPEED = 7,
+	VIRTCHNL_VF_OFFLOAD_CRC = 10,
+	VIRTCHNL_VF_OFFLOAD_TC_U32 = 11,
+	VIRTCHNL_VF_OFFLOAD_VLAN_V2 = 15,
+	VIRTCHNL_VF_OFFLOAD_VLAN = 16,
+	VIRTCHNL_VF_OFFLOAD_RX_POLLING = 17,
+	VIRTCHNL_VF_OFFLOAD_RSS_PCTYPE_V2 = 18,
+	VIRTCHNL_VF_OFFLOAD_RSS_PF = 19,
+	VIRTCHNL_VF_OFFLOAD_ENCAP = 20,
+	VIRTCHNL_VF_OFFLOAD_ENCAP_CSUM = 21,
+	VIRTCHNL_VF_OFFLOAD_RX_ENCAP_CSUM = 22,
+	VIRTCHNL_VF_OFFLOAD_ADQ = 23,
+	VIRTCHNL_VF_OFFLOAD_USO = 25,
+	VIRTCHNL_VF_OFFLOAD_RX_FLEX_DESC = 26,
+	VIRTCHNL_VF_OFFLOAD_ADV_RSS_PF = 27,
+	VIRTCHNL_VF_OFFLOAD_FDIR_PF = 28,
+	VIRTCHNL_VF_OFFLOAD_QOS = 29,
+	VIRTCHNL_VF_CAP_PTP = 31,
+
+	VIRTCHNL_VF_CAPS_MAX /* must be last */
+};
 
 struct virtchnl_vf_resource {
 	u16 num_vsis;
diff --git a/drivers/net/ethernet/intel/i40e/i40e_virtchnl_pf.c b/drivers/net/ethernet/intel/i40e/i40e_virtchnl_pf.c
index a26c3d47ec15..519f18dacd4a 100644
--- a/drivers/net/ethernet/intel/i40e/i40e_virtchnl_pf.c
+++ b/drivers/net/ethernet/intel/i40e/i40e_virtchnl_pf.c
@@ -90,7 +90,7 @@ static void i40e_set_vf_link_state(struct i40e_vf *vf,
 	if (vf->link_forced)
 		link_status = vf->link_up;
 
-	if (vf->driver_caps & VIRTCHNL_VF_CAP_ADV_LINK_SPEED) {
+	if (test_bit(VIRTCHNL_VF_CAP_ADV_LINK_SPEED, vf->driver_caps)) {
 		pfe->event_data.link_event_adv.link_speed = link_status ?
 			i40e_vc_link_speed2mbps(ls->link_speed) : 0;
 		pfe->event_data.link_event_adv.link_status = link_status;
@@ -455,8 +455,8 @@ static void i40e_config_irq_link_list(struct i40e_vf *vf, u16 vsi_id,
 	/* if the vf is running in polling mode and using interrupt zero,
 	 * need to disable auto-mask on enabling zero interrupt for VFs.
 	 */
-	if ((vf->driver_caps & VIRTCHNL_VF_OFFLOAD_RX_POLLING) &&
-	    (vector_id == 0)) {
+	if (test_bit(VIRTCHNL_VF_OFFLOAD_RX_POLLING, vf->driver_caps) &&
+	    vector_id == 0) {
 		reg = rd32(hw, I40E_GLINT_CTL);
 		if (!(reg & I40E_GLINT_CTL_DIS_AUTOMASK_VF0_MASK)) {
 			reg |= I40E_GLINT_CTL_DIS_AUTOMASK_VF0_MASK;
@@ -2146,51 +2146,56 @@ static int i40e_vc_get_vf_resources_msg(struct i40e_vf *vf, u8 *msg)
 		len = 0;
 		goto err;
 	}
-	if (VF_IS_V11(&vf->vf_ver))
-		vf->driver_caps = *(u32 *)msg;
-	else
-		vf->driver_caps = VIRTCHNL_VF_OFFLOAD_L2 |
-				  VIRTCHNL_VF_OFFLOAD_RSS_REG |
-				  VIRTCHNL_VF_OFFLOAD_VLAN;
 
-	vfres->vf_cap_flags = VIRTCHNL_VF_OFFLOAD_L2;
-	vfres->vf_cap_flags |= VIRTCHNL_VF_CAP_ADV_LINK_SPEED;
+	bitmap_zero(vf->driver_caps, VIRTCHNL_VF_CAPS_MAX);
+	if (VF_IS_V11(&vf->vf_ver)) {
+		bitmap_from_arr32(vf->driver_caps, (u32 *)msg,
+				  BITS_PER_TYPE(u32));
+	} else {
+		__set_bit(VIRTCHNL_VF_OFFLOAD_L2, vf->driver_caps);
+		__set_bit(VIRTCHNL_VF_OFFLOAD_RSS_REG, vf->driver_caps);
+		__set_bit(VIRTCHNL_VF_OFFLOAD_VLAN, vf->driver_caps);
+	}
+
+	vfres->vf_cap_flags = BIT(VIRTCHNL_VF_OFFLOAD_L2);
+	vfres->vf_cap_flags |= BIT(VIRTCHNL_VF_CAP_ADV_LINK_SPEED);
 	vsi = pf->vsi[vf->lan_vsi_idx];
 	if (!vsi->info.pvid)
-		vfres->vf_cap_flags |= VIRTCHNL_VF_OFFLOAD_VLAN;
+		vfres->vf_cap_flags |= BIT(VIRTCHNL_VF_OFFLOAD_VLAN);
 
 	if (i40e_vf_client_capable(pf, vf->vf_id) &&
-	    (vf->driver_caps & VIRTCHNL_VF_OFFLOAD_RDMA)) {
-		vfres->vf_cap_flags |= VIRTCHNL_VF_OFFLOAD_RDMA;
+	    test_bit(VIRTCHNL_VF_OFFLOAD_RDMA, vf->driver_caps)) {
+		vfres->vf_cap_flags |= BIT(VIRTCHNL_VF_OFFLOAD_RDMA);
 		set_bit(I40E_VF_STATE_RDMAENA, &vf->vf_states);
 	} else {
 		clear_bit(I40E_VF_STATE_RDMAENA, &vf->vf_states);
 	}
 
-	if (vf->driver_caps & VIRTCHNL_VF_OFFLOAD_RSS_PF) {
-		vfres->vf_cap_flags |= VIRTCHNL_VF_OFFLOAD_RSS_PF;
+	if (test_bit(VIRTCHNL_VF_OFFLOAD_RSS_PF, vf->driver_caps)) {
+		vfres->vf_cap_flags |= BIT(VIRTCHNL_VF_OFFLOAD_RSS_PF);
 	} else {
 		if (test_bit(I40E_HW_CAP_RSS_AQ, pf->hw.caps) &&
-		    (vf->driver_caps & VIRTCHNL_VF_OFFLOAD_RSS_AQ))
-			vfres->vf_cap_flags |= VIRTCHNL_VF_OFFLOAD_RSS_AQ;
+		    test_bit(VIRTCHNL_VF_OFFLOAD_RSS_AQ, vf->driver_caps))
+			vfres->vf_cap_flags |= BIT(VIRTCHNL_VF_OFFLOAD_RSS_AQ);
 		else
-			vfres->vf_cap_flags |= VIRTCHNL_VF_OFFLOAD_RSS_REG;
+			vfres->vf_cap_flags |= BIT(VIRTCHNL_VF_OFFLOAD_RSS_REG);
 	}
 
 	if (test_bit(I40E_HW_CAP_MULTI_TCP_UDP_RSS_PCTYPE, pf->hw.caps)) {
-		if (vf->driver_caps & VIRTCHNL_VF_OFFLOAD_RSS_PCTYPE_V2)
+		if (test_bit(VIRTCHNL_VF_OFFLOAD_RSS_PCTYPE_V2,
+			     vf->driver_caps))
 			vfres->vf_cap_flags |=
-				VIRTCHNL_VF_OFFLOAD_RSS_PCTYPE_V2;
+				BIT(VIRTCHNL_VF_OFFLOAD_RSS_PCTYPE_V2);
 	}
 
-	if (vf->driver_caps & VIRTCHNL_VF_OFFLOAD_ENCAP)
-		vfres->vf_cap_flags |= VIRTCHNL_VF_OFFLOAD_ENCAP;
+	if (test_bit(VIRTCHNL_VF_OFFLOAD_ENCAP, vf->driver_caps))
+		vfres->vf_cap_flags |= BIT(VIRTCHNL_VF_OFFLOAD_ENCAP);
 
 	if (test_bit(I40E_HW_CAP_OUTER_UDP_CSUM, pf->hw.caps) &&
-	    (vf->driver_caps & VIRTCHNL_VF_OFFLOAD_ENCAP_CSUM))
-		vfres->vf_cap_flags |= VIRTCHNL_VF_OFFLOAD_ENCAP_CSUM;
+	    test_bit(VIRTCHNL_VF_OFFLOAD_ENCAP_CSUM, vf->driver_caps))
+		vfres->vf_cap_flags |= BIT(VIRTCHNL_VF_OFFLOAD_ENCAP_CSUM);
 
-	if (vf->driver_caps & VIRTCHNL_VF_OFFLOAD_RX_POLLING) {
+	if (test_bit(VIRTCHNL_VF_OFFLOAD_RX_POLLING, vf->driver_caps)) {
 		if (test_bit(I40E_FLAG_MFP_ENA, pf->flags)) {
 			dev_err(&pf->pdev->dev,
 				"VF %d requested polling mode: this feature is supported only when the device is running in single function per port (SFP) mode\n",
@@ -2198,20 +2203,20 @@ static int i40e_vc_get_vf_resources_msg(struct i40e_vf *vf, u8 *msg)
 			aq_ret = -EINVAL;
 			goto err;
 		}
-		vfres->vf_cap_flags |= VIRTCHNL_VF_OFFLOAD_RX_POLLING;
+		vfres->vf_cap_flags |= BIT(VIRTCHNL_VF_OFFLOAD_RX_POLLING);
 	}
 
 	if (test_bit(I40E_HW_CAP_WB_ON_ITR, pf->hw.caps)) {
-		if (vf->driver_caps & VIRTCHNL_VF_OFFLOAD_WB_ON_ITR)
+		if (test_bit(VIRTCHNL_VF_OFFLOAD_WB_ON_ITR, vf->driver_caps))
 			vfres->vf_cap_flags |=
-					VIRTCHNL_VF_OFFLOAD_WB_ON_ITR;
+					BIT(VIRTCHNL_VF_OFFLOAD_WB_ON_ITR);
 	}
 
-	if (vf->driver_caps & VIRTCHNL_VF_OFFLOAD_REQ_QUEUES)
-		vfres->vf_cap_flags |= VIRTCHNL_VF_OFFLOAD_REQ_QUEUES;
+	if (test_bit(VIRTCHNL_VF_OFFLOAD_REQ_QUEUES, vf->driver_caps))
+		vfres->vf_cap_flags |= BIT(VIRTCHNL_VF_OFFLOAD_REQ_QUEUES);
 
-	if (vf->driver_caps & VIRTCHNL_VF_OFFLOAD_ADQ)
-		vfres->vf_cap_flags |= VIRTCHNL_VF_OFFLOAD_ADQ;
+	if (test_bit(VIRTCHNL_VF_OFFLOAD_ADQ, vf->driver_caps))
+		vfres->vf_cap_flags |= BIT(VIRTCHNL_VF_OFFLOAD_ADQ);
 
 	vfres->num_vsis = num_vsis;
 	vfres->num_queue_pairs = vf->num_queue_pairs;
@@ -2227,7 +2232,8 @@ static int i40e_vc_get_vf_resources_msg(struct i40e_vf *vf, u8 *msg)
 		/* VFs only use TC 0 */
 		vfres->vsi_res[0].qset_handle
 					  = le16_to_cpu(vsi->info.qs_handle[0]);
-		if (!(vf->driver_caps & VIRTCHNL_VF_OFFLOAD_USO) && !vf->pf_set_mac) {
+		if (!test_bit(VIRTCHNL_VF_OFFLOAD_USO, vf->driver_caps) &&
+		    !vf->pf_set_mac) {
 			spin_lock_bh(&vsi->mac_filter_hash_lock);
 			i40e_del_mac_filter(vsi, vf->default_lan_addr.addr);
 			eth_zero_addr(vf->default_lan_addr.addr);
@@ -4059,7 +4065,7 @@ static int i40e_vc_add_qch_msg(struct i40e_vf *vf, u8 *msg)
 		goto err;
 	}
 
-	if (!(vf->driver_caps & VIRTCHNL_VF_OFFLOAD_ADQ)) {
+	if (!test_bit(VIRTCHNL_VF_OFFLOAD_ADQ, vf->driver_caps)) {
 		dev_err(&pf->pdev->dev,
 			"VF %d attempting to enable ADq, but hasn't properly negotiated that capability\n",
 			vf->vf_id);
diff --git a/drivers/net/ethernet/intel/iavf/iavf_common.c b/drivers/net/ethernet/intel/iavf/iavf_common.c
index 9bc8bdc339c7..97e8b4ac8125 100644
--- a/drivers/net/ethernet/intel/iavf/iavf_common.c
+++ b/drivers/net/ethernet/intel/iavf/iavf_common.c
@@ -421,12 +421,14 @@ enum iavf_status iavf_aq_send_msg_to_pf(struct iavf_hw *hw,
  * iavf_vf_parse_hw_config
  * @hw: pointer to the hardware structure
  * @msg: pointer to the virtual channel VF resource structure
+ * @vf_cap_flags: VF capability flags
  *
  * Given a VF resource message from the PF, populate the hw struct
  * with appropriate information.
  **/
 void iavf_vf_parse_hw_config(struct iavf_hw *hw,
-			     struct virtchnl_vf_resource *msg)
+			     struct virtchnl_vf_resource *msg,
+			     const unsigned long *vf_cap_flags)
 {
 	struct virtchnl_vsi_resource *vsi_res;
 	int i;
@@ -437,8 +439,7 @@ void iavf_vf_parse_hw_config(struct iavf_hw *hw,
 	hw->dev_caps.num_rx_qp = msg->num_queue_pairs;
 	hw->dev_caps.num_tx_qp = msg->num_queue_pairs;
 	hw->dev_caps.num_msix_vectors_vf = msg->max_vectors;
-	hw->dev_caps.dcb = msg->vf_cap_flags &
-			   VIRTCHNL_VF_OFFLOAD_L2;
+	hw->dev_caps.dcb = test_bit(VIRTCHNL_VF_OFFLOAD_L2, vf_cap_flags);
 	hw->dev_caps.fcoe = 0;
 	for (i = 0; i < msg->num_vsis; i++) {
 		if (vsi_res->vsi_type == VIRTCHNL_VSI_SRIOV) {
diff --git a/drivers/net/ethernet/intel/iavf/iavf_ethtool.c b/drivers/net/ethernet/intel/iavf/iavf_ethtool.c
index dc2503e3c5ba..b14d5c2309cc 100644
--- a/drivers/net/ethernet/intel/iavf/iavf_ethtool.c
+++ b/drivers/net/ethernet/intel/iavf/iavf_ethtool.c
@@ -1726,7 +1726,7 @@ static int iavf_set_channels(struct net_device *netdev,
 	struct iavf_adapter *adapter = netdev_priv(netdev);
 	u32 num_req = ch->combined_count;
 
-	if ((adapter->vf_res->vf_cap_flags & VIRTCHNL_VF_OFFLOAD_ADQ) &&
+	if (test_bit(VIRTCHNL_VF_OFFLOAD_ADQ, adapter->vf_cap_flags) &&
 	    adapter->num_tc) {
 		dev_info(&adapter->pdev->dev, "Cannot set channels since ADq is enabled.\n");
 		return -EINVAL;
diff --git a/drivers/net/ethernet/intel/iavf/iavf_main.c b/drivers/net/ethernet/intel/iavf/iavf_main.c
index 30ae1a296115..ce2b61e56f9d 100644
--- a/drivers/net/ethernet/intel/iavf/iavf_main.c
+++ b/drivers/net/ethernet/intel/iavf/iavf_main.c
@@ -1533,7 +1533,7 @@ static int iavf_alloc_queues(struct iavf_adapter *adapter)
 	 */
 	if (adapter->num_req_queues)
 		num_active_queues = adapter->num_req_queues;
-	else if ((adapter->vf_res->vf_cap_flags & VIRTCHNL_VF_OFFLOAD_ADQ) &&
+	else if (test_bit(VIRTCHNL_VF_OFFLOAD_ADQ, adapter->vf_cap_flags) &&
 		 adapter->num_tc)
 		num_active_queues = adapter->ch_config.total_qps;
 	else
@@ -1736,8 +1736,8 @@ static int iavf_init_rss(struct iavf_adapter *adapter)
 
 	if (!RSS_PF(adapter)) {
 		/* Enable PCTYPES for RSS, TCP/UDP with IPv4/IPv6 */
-		if (adapter->vf_res->vf_cap_flags &
-		    VIRTCHNL_VF_OFFLOAD_RSS_PCTYPE_V2)
+		if (test_bit(VIRTCHNL_VF_OFFLOAD_RSS_PCTYPE_V2,
+			     adapter->vf_cap_flags))
 			adapter->rss_hashcfg =
 				IAVF_DEFAULT_RSS_HASHCFG_EXPANDED;
 		else
@@ -1861,7 +1861,7 @@ static int iavf_init_interrupt_scheme(struct iavf_adapter *adapter)
 	 * resources have been allocated in the reset path.
 	 * Now we can truly claim that ADq is enabled.
 	 */
-	if ((adapter->vf_res->vf_cap_flags & VIRTCHNL_VF_OFFLOAD_ADQ) &&
+	if (test_bit(VIRTCHNL_VF_OFFLOAD_ADQ, adapter->vf_cap_flags) &&
 	    adapter->num_tc)
 		dev_info(&adapter->pdev->dev, "ADq Enabled, %u TCs created",
 			 adapter->num_tc);
@@ -2436,7 +2436,7 @@ int iavf_parse_vf_resource_msg(struct iavf_adapter *adapter)
 	adapter->vsi.base_vector = 1;
 	vsi->netdev = adapter->netdev;
 	vsi->qs_handle = adapter->vsi_res->qset_handle;
-	if (adapter->vf_res->vf_cap_flags & VIRTCHNL_VF_OFFLOAD_RSS_PF) {
+	if (test_bit(VIRTCHNL_VF_OFFLOAD_RSS_PF, adapter->vf_cap_flags)) {
 		adapter->rss_key_size = adapter->vf_res->rss_key_size;
 		adapter->rss_lut_size = adapter->vf_res->rss_lut_size;
 	} else {
@@ -2783,8 +2783,7 @@ static void iavf_init_config_adapter(struct iavf_adapter *adapter)
 	if (err)
 		goto err_sw_init;
 	iavf_map_rings_to_vectors(adapter);
-	if (adapter->vf_res->vf_cap_flags &
-		VIRTCHNL_VF_OFFLOAD_WB_ON_ITR)
+	if (test_bit(VIRTCHNL_VF_OFFLOAD_WB_ON_ITR, adapter->vf_cap_flags))
 		adapter->flags |= IAVF_FLAG_WB_ON_ITR_CAPABLE;
 
 	err = iavf_request_misc_irq(adapter);
@@ -3080,7 +3079,6 @@ static void iavf_reconfig_qs_bw(struct iavf_adapter *adapter)
  */
 void iavf_reset_step(struct iavf_adapter *adapter)
 {
-	struct virtchnl_vf_resource *vfres = adapter->vf_res;
 	struct net_device *netdev = adapter->netdev;
 	struct iavf_hw *hw = &adapter->hw;
 	struct iavf_mac_filter *f, *ftmp;
@@ -3234,7 +3232,7 @@ void iavf_reset_step(struct iavf_adapter *adapter)
 
 	/* check if TCs are running and re-add all cloud filters */
 	spin_lock_bh(&adapter->cloud_filter_list_lock);
-	if ((vfres->vf_cap_flags & VIRTCHNL_VF_OFFLOAD_ADQ) &&
+	if (test_bit(VIRTCHNL_VF_OFFLOAD_ADQ, adapter->vf_cap_flags) &&
 	    adapter->num_tc) {
 		list_for_each_entry(cf, &adapter->cloud_filter_list, list) {
 			cf->add = true;
@@ -3675,7 +3673,6 @@ static int __iavf_setup_tc(struct net_device *netdev, void *type_data)
 {
 	struct tc_mqprio_qopt_offload *mqprio_qopt = type_data;
 	struct iavf_adapter *adapter = netdev_priv(netdev);
-	struct virtchnl_vf_resource *vfres = adapter->vf_res;
 	u8 num_tc = 0, total_qps = 0;
 	int ret = 0, netdev_tc = 0;
 	u64 max_tx_rate;
@@ -3704,7 +3701,7 @@ static int __iavf_setup_tc(struct net_device *netdev, void *type_data)
 
 	/* add queue channel */
 	if (mode == TC_MQPRIO_MODE_CHANNEL) {
-		if (!(vfres->vf_cap_flags & VIRTCHNL_VF_OFFLOAD_ADQ)) {
+		if (!test_bit(VIRTCHNL_VF_OFFLOAD_ADQ, adapter->vf_cap_flags)) {
 			dev_err(&adapter->pdev->dev, "ADq not supported\n");
 			return -EOPNOTSUPP;
 		}
@@ -4744,7 +4741,7 @@ iavf_get_netdev_vlan_hw_features(struct iavf_adapter *adapter)
 {
 	netdev_features_t hw_features = 0;
 
-	if (!adapter->vf_res || !adapter->vf_res->vf_cap_flags)
+	if (bitmap_empty(adapter->vf_cap_flags, VIRTCHNL_VF_CAPS_MAX))
 		return hw_features;
 
 	/* Enable VLAN features if supported */
@@ -4809,7 +4806,7 @@ iavf_get_netdev_vlan_features(struct iavf_adapter *adapter)
 {
 	netdev_features_t features = 0;
 
-	if (!adapter->vf_res || !adapter->vf_res->vf_cap_flags)
+	if (bitmap_empty(adapter->vf_cap_flags, VIRTCHNL_VF_CAPS_MAX))
 		return features;
 
 	if (VLAN_ALLOWED(adapter)) {
@@ -5186,7 +5183,6 @@ static int iavf_check_reset_complete(struct iavf_hw *hw)
  **/
 int iavf_process_config(struct iavf_adapter *adapter)
 {
-	struct virtchnl_vf_resource *vfres = adapter->vf_res;
 	netdev_features_t hw_vlan_features, vlan_features;
 	struct net_device *netdev = adapter->netdev;
 	netdev_features_t hw_enc_features;
@@ -5208,7 +5204,7 @@ int iavf_process_config(struct iavf_adapter *adapter)
 	/* advertise to stack only if offloads for encapsulated packets is
 	 * supported
 	 */
-	if (vfres->vf_cap_flags & VIRTCHNL_VF_OFFLOAD_ENCAP) {
+	if (test_bit(VIRTCHNL_VF_OFFLOAD_ENCAP, adapter->vf_cap_flags)) {
 		hw_enc_features |= NETIF_F_GSO_UDP_TUNNEL	|
 				   NETIF_F_GSO_GRE		|
 				   NETIF_F_GSO_GRE_CSUM		|
@@ -5218,8 +5214,8 @@ int iavf_process_config(struct iavf_adapter *adapter)
 				   NETIF_F_GSO_PARTIAL		|
 				   0;
 
-		if (!(vfres->vf_cap_flags &
-		      VIRTCHNL_VF_OFFLOAD_ENCAP_CSUM))
+		if (!test_bit(VIRTCHNL_VF_OFFLOAD_ENCAP_CSUM,
+			      adapter->vf_cap_flags))
 			netdev->gso_partial_features |=
 				NETIF_F_GSO_UDP_TUNNEL_CSUM;
 
@@ -5239,11 +5235,11 @@ int iavf_process_config(struct iavf_adapter *adapter)
 	hw_vlan_features = iavf_get_netdev_vlan_hw_features(adapter);
 
 	/* Enable HW TC offload if ADQ or tc U32 is supported */
-	if (vfres->vf_cap_flags & VIRTCHNL_VF_OFFLOAD_ADQ ||
+	if (test_bit(VIRTCHNL_VF_OFFLOAD_ADQ, adapter->vf_cap_flags) ||
 	    TC_U32_SUPPORT(adapter))
 		hw_features |= NETIF_F_HW_TC;
 
-	if (vfres->vf_cap_flags & VIRTCHNL_VF_OFFLOAD_USO)
+	if (test_bit(VIRTCHNL_VF_OFFLOAD_USO, adapter->vf_cap_flags))
 		hw_features |= NETIF_F_GSO_UDP_L4;
 
 	netdev->hw_features |= hw_features | hw_vlan_features;
@@ -5251,7 +5247,7 @@ int iavf_process_config(struct iavf_adapter *adapter)
 
 	netdev->features |= hw_features | vlan_features;
 
-	if (vfres->vf_cap_flags & VIRTCHNL_VF_OFFLOAD_VLAN)
+	if (test_bit(VIRTCHNL_VF_OFFLOAD_VLAN, adapter->vf_cap_flags))
 		netdev->features |= NETIF_F_HW_VLAN_CTAG_FILTER;
 
 	if (FDIR_FLTR_SUPPORT(adapter)) {
diff --git a/drivers/net/ethernet/intel/iavf/iavf_virtchnl.c b/drivers/net/ethernet/intel/iavf/iavf_virtchnl.c
index 04dc447bb8b0..38be4caf77f0 100644
--- a/drivers/net/ethernet/intel/iavf/iavf_virtchnl.c
+++ b/drivers/net/ethernet/intel/iavf/iavf_virtchnl.c
@@ -152,27 +152,27 @@ int iavf_send_vf_config_msg(struct iavf_adapter *adapter)
 {
 	u32 caps;
 
-	caps = VIRTCHNL_VF_OFFLOAD_L2 |
-	       VIRTCHNL_VF_OFFLOAD_RSS_PF |
-	       VIRTCHNL_VF_OFFLOAD_RSS_AQ |
-	       VIRTCHNL_VF_OFFLOAD_RSS_REG |
-	       VIRTCHNL_VF_OFFLOAD_VLAN |
-	       VIRTCHNL_VF_OFFLOAD_WB_ON_ITR |
-	       VIRTCHNL_VF_OFFLOAD_RSS_PCTYPE_V2 |
-	       VIRTCHNL_VF_OFFLOAD_ENCAP |
-	       VIRTCHNL_VF_OFFLOAD_TC_U32 |
-	       VIRTCHNL_VF_OFFLOAD_VLAN_V2 |
-	       VIRTCHNL_VF_OFFLOAD_RX_FLEX_DESC |
-	       VIRTCHNL_VF_OFFLOAD_CRC |
-	       VIRTCHNL_VF_OFFLOAD_ENCAP_CSUM |
-	       VIRTCHNL_VF_OFFLOAD_REQ_QUEUES |
-	       VIRTCHNL_VF_CAP_PTP |
-	       VIRTCHNL_VF_OFFLOAD_ADQ |
-	       VIRTCHNL_VF_OFFLOAD_USO |
-	       VIRTCHNL_VF_OFFLOAD_FDIR_PF |
-	       VIRTCHNL_VF_OFFLOAD_ADV_RSS_PF |
-	       VIRTCHNL_VF_CAP_ADV_LINK_SPEED |
-	       VIRTCHNL_VF_OFFLOAD_QOS;
+	caps = BIT(VIRTCHNL_VF_OFFLOAD_L2) |
+	       BIT(VIRTCHNL_VF_OFFLOAD_RSS_PF) |
+	       BIT(VIRTCHNL_VF_OFFLOAD_RSS_AQ) |
+	       BIT(VIRTCHNL_VF_OFFLOAD_RSS_REG) |
+	       BIT(VIRTCHNL_VF_OFFLOAD_VLAN) |
+	       BIT(VIRTCHNL_VF_OFFLOAD_WB_ON_ITR) |
+	       BIT(VIRTCHNL_VF_OFFLOAD_RSS_PCTYPE_V2) |
+	       BIT(VIRTCHNL_VF_OFFLOAD_ENCAP) |
+	       BIT(VIRTCHNL_VF_OFFLOAD_TC_U32) |
+	       BIT(VIRTCHNL_VF_OFFLOAD_VLAN_V2) |
+	       BIT(VIRTCHNL_VF_OFFLOAD_RX_FLEX_DESC) |
+	       BIT(VIRTCHNL_VF_OFFLOAD_CRC) |
+	       BIT(VIRTCHNL_VF_OFFLOAD_ENCAP_CSUM) |
+	       BIT(VIRTCHNL_VF_OFFLOAD_REQ_QUEUES) |
+	       BIT(VIRTCHNL_VF_CAP_PTP) |
+	       BIT(VIRTCHNL_VF_OFFLOAD_ADQ) |
+	       BIT(VIRTCHNL_VF_OFFLOAD_USO) |
+	       BIT(VIRTCHNL_VF_OFFLOAD_FDIR_PF) |
+	       BIT(VIRTCHNL_VF_OFFLOAD_ADV_RSS_PF) |
+	       BIT(VIRTCHNL_VF_CAP_ADV_LINK_SPEED) |
+	       BIT(VIRTCHNL_VF_OFFLOAD_QOS);
 
 	adapter->current_op = VIRTCHNL_OP_GET_VF_RESOURCES;
 	adapter->aq_required &= ~IAVF_FLAG_AQ_GET_CONFIG;
@@ -296,12 +296,16 @@ int iavf_get_vf_config(struct iavf_adapter *adapter)
 	err = iavf_poll_virtchnl_msg(hw, &event, VIRTCHNL_OP_GET_VF_RESOURCES);
 	memcpy(adapter->vf_res, event.msg_buf, min(event.msg_len, len));
 
+	/* mirror to the extended bitmap */
+	bitmap_from_arr32(adapter->vf_cap_flags, &adapter->vf_res->vf_cap_flags,
+			  BITS_PER_TYPE(u32));
+
 	/* some PFs send more queues than we should have so validate that
 	 * we aren't getting too many queues
 	 */
 	if (!err)
 		iavf_validate_num_queues(adapter);
-	iavf_vf_parse_hw_config(hw, adapter->vf_res);
+	iavf_vf_parse_hw_config(hw, adapter->vf_res, adapter->vf_cap_flags);
 
 	kfree(event.msg_buf);
 
@@ -2579,7 +2583,8 @@ void iavf_virtchnl_completion(struct iavf_adapter *adapter,
 
 		memcpy(adapter->vf_res, msg, min(msglen, len));
 		iavf_validate_num_queues(adapter);
-		iavf_vf_parse_hw_config(&adapter->hw, adapter->vf_res);
+		iavf_vf_parse_hw_config(&adapter->hw, adapter->vf_res,
+					adapter->vf_cap_flags);
 		if (is_zero_ether_addr(adapter->hw.mac.addr)) {
 			/* restore current mac address */
 			ether_addr_copy(adapter->hw.mac.addr, netdev->dev_addr);
diff --git a/drivers/net/ethernet/intel/ice/ice_vf_lib.c b/drivers/net/ethernet/intel/ice/ice_vf_lib.c
index 64a4a9eac9db..6570480c13fa 100644
--- a/drivers/net/ethernet/intel/ice/ice_vf_lib.c
+++ b/drivers/net/ethernet/intel/ice/ice_vf_lib.c
@@ -792,7 +792,7 @@ void ice_reset_all_vfs(struct ice_pf *pf)
 		mutex_lock(&vf->cfg_lock);
 
 		ice_eswitch_detach_vf(pf, vf);
-		vf->driver_caps = 0;
+		bitmap_zero(vf->driver_caps, VIRTCHNL_VF_CAPS_MAX);
 		ice_vc_set_default_allowlist(vf);
 
 		ice_vf_fdir_exit(vf);
@@ -935,7 +935,7 @@ int ice_reset_vf(struct ice_vf *vf, u32 flags)
 	if (!rsd)
 		dev_warn(dev, "VF reset check timeout on VF %d\n", vf->vf_id);
 
-	vf->driver_caps = 0;
+	bitmap_zero(vf->driver_caps, VIRTCHNL_VF_CAPS_MAX);
 	ice_vc_set_default_allowlist(vf);
 
 	/* disable promiscuous modes in case they were enabled
diff --git a/drivers/net/ethernet/intel/ice/virt/allowlist.c b/drivers/net/ethernet/intel/ice/virt/allowlist.c
index a07efec19c45..eb3d3b4e65a9 100644
--- a/drivers/net/ethernet/intel/ice/virt/allowlist.c
+++ b/drivers/net/ethernet/intel/ice/virt/allowlist.c
@@ -102,7 +102,7 @@ struct allowlist_opcode_info {
 
 #define BIT_INDEX(caps) (HWEIGHT((caps) - 1))
 #define ALLOW_ITEM(caps, list) \
-	[BIT_INDEX(caps)] = { \
+	[caps] = { \
 		.opcodes = list, \
 		.size = ARRAY_SIZE(list) \
 	}
@@ -190,10 +190,9 @@ void ice_vc_set_working_allowlist(struct ice_vf *vf)
  */
 void ice_vc_set_caps_allowlist(struct ice_vf *vf)
 {
-	unsigned long caps = vf->driver_caps;
 	unsigned int i;
 
-	for_each_set_bit(i, &caps, ARRAY_SIZE(allowlist_opcodes))
+	for_each_set_bit(i, vf->driver_caps, ARRAY_SIZE(allowlist_opcodes))
 		ice_vc_allowlist_opcodes(vf, allowlist_opcodes[i].opcodes,
 					 allowlist_opcodes[i].size);
 }
diff --git a/drivers/net/ethernet/intel/ice/virt/fdir.c b/drivers/net/ethernet/intel/ice/virt/fdir.c
index 4f1f3442e52c..74967edf27ad 100644
--- a/drivers/net/ethernet/intel/ice/virt/fdir.c
+++ b/drivers/net/ethernet/intel/ice/virt/fdir.c
@@ -105,7 +105,7 @@ ice_vc_fdir_param_check(struct ice_vf *vf, u16 vsi_id)
 	if (!test_bit(ICE_VF_STATE_ACTIVE, vf->vf_states))
 		return -EINVAL;
 
-	if (!(vf->driver_caps & VIRTCHNL_VF_OFFLOAD_FDIR_PF))
+	if (!test_bit(VIRTCHNL_VF_OFFLOAD_FDIR_PF, vf->driver_caps))
 		return -EINVAL;
 
 	if (!ice_vc_isvalid_vsi_id(vf, vsi_id))
diff --git a/drivers/net/ethernet/intel/ice/virt/queues.c b/drivers/net/ethernet/intel/ice/virt/queues.c
index 31be2f76181c..4e2caeb5d642 100644
--- a/drivers/net/ethernet/intel/ice/virt/queues.c
+++ b/drivers/net/ethernet/intel/ice/virt/queues.c
@@ -781,7 +781,7 @@ int ice_vc_cfg_qs_msg(struct ice_vf *vf, u8 *msg)
 		if (!qci->qpair[i].rxq.crc_disable)
 			continue;
 
-		if (!(vf->driver_caps & VIRTCHNL_VF_OFFLOAD_CRC) ||
+		if (!test_bit(VIRTCHNL_VF_OFFLOAD_CRC, vf->driver_caps) ||
 		    vf->vlan_strip_ena)
 			goto error_param;
 	}
@@ -868,8 +868,8 @@ int ice_vc_cfg_qs_msg(struct ice_vf *vf, u8 *msg)
 			 * format. Legacy 16byte descriptor is not supported.
 			 * If this RXDID is selected, return error.
 			 */
-			if (vf->driver_caps &
-			    VIRTCHNL_VF_OFFLOAD_RX_FLEX_DESC) {
+			if (test_bit(VIRTCHNL_VF_OFFLOAD_RX_FLEX_DESC,
+				     vf->driver_caps)) {
 				rxdid = qpi->rxq.rxdid;
 				if (!(BIT(rxdid) & pf->supported_rxdids))
 					goto error_param;
@@ -877,9 +877,10 @@ int ice_vc_cfg_qs_msg(struct ice_vf *vf, u8 *msg)
 				rxdid = ICE_RXDID_LEGACY_1;
 			}
 
-			ena_ts = ((vf->driver_caps &
-				  VIRTCHNL_VF_OFFLOAD_RX_FLEX_DESC) &&
-				  (vf->driver_caps & VIRTCHNL_VF_CAP_PTP) &&
+			ena_ts = (test_bit(VIRTCHNL_VF_OFFLOAD_RX_FLEX_DESC,
+					   vf->driver_caps) &&
+				  test_bit(VIRTCHNL_VF_CAP_PTP,
+					   vf->driver_caps) &&
 				  (qpi->rxq.flags & VIRTCHNL_PTP_RX_TSTAMP));
 
 			ice_write_qrxflxp_cntxt(&vsi->back->hw,
diff --git a/drivers/net/ethernet/intel/ice/virt/rss.c b/drivers/net/ethernet/intel/ice/virt/rss.c
index 960012ca91b5..b4f6dcce76f6 100644
--- a/drivers/net/ethernet/intel/ice/virt/rss.c
+++ b/drivers/net/ethernet/intel/ice/virt/rss.c
@@ -680,9 +680,9 @@ static bool ice_vc_parse_rss_cfg(struct ice_hw *hw,
  * Return true if VIRTCHNL_VF_OFFLOAD_ADV_RSS_PF capability is set,
  * else return false
  */
-static bool ice_vf_adv_rss_offload_ena(u32 caps)
+static bool ice_vf_adv_rss_offload_ena(const unsigned long *caps)
 {
-	return !!(caps & VIRTCHNL_VF_OFFLOAD_ADV_RSS_PF);
+	return test_bit(VIRTCHNL_VF_OFFLOAD_ADV_RSS_PF, caps);
 }
 
 /**
diff --git a/drivers/net/ethernet/intel/ice/virt/virtchnl.c b/drivers/net/ethernet/intel/ice/virt/virtchnl.c
index 06d2f9be93ae..f15e8c82d52d 100644
--- a/drivers/net/ethernet/intel/ice/virt/virtchnl.c
+++ b/drivers/net/ethernet/intel/ice/virt/virtchnl.c
@@ -58,7 +58,7 @@ static void
 ice_set_pfe_link(struct ice_vf *vf, struct virtchnl_pf_event *pfe,
 		 int ice_link_speed, bool link_up)
 {
-	if (vf->driver_caps & VIRTCHNL_VF_CAP_ADV_LINK_SPEED) {
+	if (test_bit(VIRTCHNL_VF_CAP_ADV_LINK_SPEED, vf->driver_caps)) {
 		pfe->event_data.link_event_adv.link_status = link_up;
 		/* Speed in Mbps */
 		pfe->event_data.link_event_adv.link_speed =
@@ -188,26 +188,26 @@ static int ice_vc_get_ver_msg(struct ice_vf *vf, u8 *msg)
 }
 
 /**
- * ice_vc_get_vlan_caps
+ * ice_vc_get_vlan_caps - get VF capability flags based on driver caps
  * @hw: pointer to the hw
  * @vf: pointer to the VF info
  * @vsi: pointer to the VSI
  * @driver_caps: current driver caps
  *
- * Return 0 if there is no VLAN caps supported, or VLAN caps value
+ * Return: 0 if there is no VLAN caps supported, or VLAN caps value
  */
 static u32
 ice_vc_get_vlan_caps(struct ice_hw *hw, struct ice_vf *vf, struct ice_vsi *vsi,
-		     u32 driver_caps)
+		     const unsigned long *driver_caps)
 {
 	if (ice_is_eswitch_mode_switchdev(vf->pf))
 		/* In switchdev setting VLAN from VF isn't supported */
 		return 0;
 
-	if (driver_caps & VIRTCHNL_VF_OFFLOAD_VLAN_V2) {
+	if (test_bit(VIRTCHNL_VF_OFFLOAD_VLAN_V2, driver_caps)) {
 		/* VLAN offloads based on current device configuration */
-		return VIRTCHNL_VF_OFFLOAD_VLAN_V2;
-	} else if (driver_caps & VIRTCHNL_VF_OFFLOAD_VLAN) {
+		return BIT(VIRTCHNL_VF_OFFLOAD_VLAN_V2);
+	} else if (test_bit(VIRTCHNL_VF_OFFLOAD_VLAN, driver_caps)) {
 		/* allow VF to negotiate VIRTCHNL_VF_OFFLOAD explicitly for
 		 * these two conditions, which amounts to guest VLAN filtering
 		 * and offloads being based on the inner VLAN or the
@@ -215,7 +215,7 @@ ice_vc_get_vlan_caps(struct ice_hw *hw, struct ice_vf *vf, struct ice_vsi *vsi,
 		 * negotiate VIRTCHNL_VF_OFFLOAD in any other cases
 		 */
 		if (ice_is_dvm_ena(hw) && ice_vf_is_port_vlan_ena(vf)) {
-			return VIRTCHNL_VF_OFFLOAD_VLAN;
+			return BIT(VIRTCHNL_VF_OFFLOAD_VLAN);
 		} else if (!ice_is_dvm_ena(hw) &&
 			   !ice_vf_is_port_vlan_ena(vf)) {
 			/* configure backward compatible support for VFs that
@@ -223,7 +223,7 @@ ice_vc_get_vlan_caps(struct ice_hw *hw, struct ice_vf *vf, struct ice_vsi *vsi,
 			 * configured in SVM, and no port VLAN is configured
 			 */
 			ice_vf_vsi_cfg_svm_legacy_vlan_mode(vsi);
-			return VIRTCHNL_VF_OFFLOAD_VLAN;
+			return BIT(VIRTCHNL_VF_OFFLOAD_VLAN);
 		} else if (ice_is_dvm_ena(hw)) {
 			/* configure software offloaded VLAN support when DVM
 			 * is enabled, but no port VLAN is enabled
@@ -264,13 +264,17 @@ static int ice_vc_get_vf_res_msg(struct ice_vf *vf, u8 *msg)
 		len = 0;
 		goto err;
 	}
-	if (VF_IS_V11(&vf->vf_ver))
-		vf->driver_caps = *(u32 *)msg;
-	else
-		vf->driver_caps = VIRTCHNL_VF_OFFLOAD_L2 |
-				  VIRTCHNL_VF_OFFLOAD_VLAN;
 
-	vfres->vf_cap_flags = VIRTCHNL_VF_OFFLOAD_L2;
+	bitmap_zero(vf->driver_caps, VIRTCHNL_VF_CAPS_MAX);
+	if (VF_IS_V11(&vf->vf_ver)) {
+		bitmap_from_arr32(vf->driver_caps, (u32 *)msg,
+				  BITS_PER_TYPE(u32));
+	} else {
+		__set_bit(VIRTCHNL_VF_OFFLOAD_L2, vf->driver_caps);
+		__set_bit(VIRTCHNL_VF_OFFLOAD_VLAN, vf->driver_caps);
+	}
+
+	vfres->vf_cap_flags = BIT(VIRTCHNL_VF_OFFLOAD_L2);
 	vsi = ice_get_vf_vsi(vf);
 	if (!vsi) {
 		v_ret = VIRTCHNL_STATUS_ERR_PARAM;
@@ -280,54 +284,54 @@ static int ice_vc_get_vf_res_msg(struct ice_vf *vf, u8 *msg)
 	vfres->vf_cap_flags |= ice_vc_get_vlan_caps(hw, vf, vsi,
 						    vf->driver_caps);
 
-	if (vf->driver_caps & VIRTCHNL_VF_OFFLOAD_RSS_PF)
-		vfres->vf_cap_flags |= VIRTCHNL_VF_OFFLOAD_RSS_PF;
+	if (test_bit(VIRTCHNL_VF_OFFLOAD_RSS_PF, vf->driver_caps))
+		vfres->vf_cap_flags |= BIT(VIRTCHNL_VF_OFFLOAD_RSS_PF);
 
-	if (vf->driver_caps & VIRTCHNL_VF_OFFLOAD_RX_FLEX_DESC)
-		vfres->vf_cap_flags |= VIRTCHNL_VF_OFFLOAD_RX_FLEX_DESC;
+	if (test_bit(VIRTCHNL_VF_OFFLOAD_RX_FLEX_DESC, vf->driver_caps))
+		vfres->vf_cap_flags |= BIT(VIRTCHNL_VF_OFFLOAD_RX_FLEX_DESC);
 
-	if (vf->driver_caps & VIRTCHNL_VF_OFFLOAD_FDIR_PF)
-		vfres->vf_cap_flags |= VIRTCHNL_VF_OFFLOAD_FDIR_PF;
+	if (test_bit(VIRTCHNL_VF_OFFLOAD_FDIR_PF, vf->driver_caps))
+		vfres->vf_cap_flags |= BIT(VIRTCHNL_VF_OFFLOAD_FDIR_PF);
 
-	if (vf->driver_caps & VIRTCHNL_VF_OFFLOAD_TC_U32 &&
-	    vfres->vf_cap_flags & VIRTCHNL_VF_OFFLOAD_FDIR_PF)
-		vfres->vf_cap_flags |= VIRTCHNL_VF_OFFLOAD_TC_U32;
+	if (test_bit(VIRTCHNL_VF_OFFLOAD_TC_U32, vf->driver_caps) &&
+	    (vfres->vf_cap_flags & BIT(VIRTCHNL_VF_OFFLOAD_FDIR_PF)))
+		vfres->vf_cap_flags |= BIT(VIRTCHNL_VF_OFFLOAD_TC_U32);
 
-	if (vf->driver_caps & VIRTCHNL_VF_OFFLOAD_RSS_PCTYPE_V2)
-		vfres->vf_cap_flags |= VIRTCHNL_VF_OFFLOAD_RSS_PCTYPE_V2;
+	if (test_bit(VIRTCHNL_VF_OFFLOAD_RSS_PCTYPE_V2, vf->driver_caps))
+		vfres->vf_cap_flags |= BIT(VIRTCHNL_VF_OFFLOAD_RSS_PCTYPE_V2);
 
-	if (vf->driver_caps & VIRTCHNL_VF_OFFLOAD_ENCAP)
-		vfres->vf_cap_flags |= VIRTCHNL_VF_OFFLOAD_ENCAP;
+	if (test_bit(VIRTCHNL_VF_OFFLOAD_ENCAP, vf->driver_caps))
+		vfres->vf_cap_flags |= BIT(VIRTCHNL_VF_OFFLOAD_ENCAP);
 
-	if (vf->driver_caps & VIRTCHNL_VF_OFFLOAD_ENCAP_CSUM)
-		vfres->vf_cap_flags |= VIRTCHNL_VF_OFFLOAD_ENCAP_CSUM;
+	if (test_bit(VIRTCHNL_VF_OFFLOAD_ENCAP_CSUM, vf->driver_caps))
+		vfres->vf_cap_flags |= BIT(VIRTCHNL_VF_OFFLOAD_ENCAP_CSUM);
 
-	if (vf->driver_caps & VIRTCHNL_VF_OFFLOAD_RX_POLLING)
-		vfres->vf_cap_flags |= VIRTCHNL_VF_OFFLOAD_RX_POLLING;
+	if (test_bit(VIRTCHNL_VF_OFFLOAD_RX_POLLING, vf->driver_caps))
+		vfres->vf_cap_flags |= BIT(VIRTCHNL_VF_OFFLOAD_RX_POLLING);
 
-	if (vf->driver_caps & VIRTCHNL_VF_OFFLOAD_WB_ON_ITR)
-		vfres->vf_cap_flags |= VIRTCHNL_VF_OFFLOAD_WB_ON_ITR;
+	if (test_bit(VIRTCHNL_VF_OFFLOAD_WB_ON_ITR, vf->driver_caps))
+		vfres->vf_cap_flags |= BIT(VIRTCHNL_VF_OFFLOAD_WB_ON_ITR);
 
-	if (vf->driver_caps & VIRTCHNL_VF_OFFLOAD_REQ_QUEUES)
-		vfres->vf_cap_flags |= VIRTCHNL_VF_OFFLOAD_REQ_QUEUES;
+	if (test_bit(VIRTCHNL_VF_OFFLOAD_REQ_QUEUES, vf->driver_caps))
+		vfres->vf_cap_flags |= BIT(VIRTCHNL_VF_OFFLOAD_REQ_QUEUES);
 
-	if (vf->driver_caps & VIRTCHNL_VF_OFFLOAD_CRC)
-		vfres->vf_cap_flags |= VIRTCHNL_VF_OFFLOAD_CRC;
+	if (test_bit(VIRTCHNL_VF_OFFLOAD_CRC, vf->driver_caps))
+		vfres->vf_cap_flags |= BIT(VIRTCHNL_VF_OFFLOAD_CRC);
 
-	if (vf->driver_caps & VIRTCHNL_VF_CAP_ADV_LINK_SPEED)
-		vfres->vf_cap_flags |= VIRTCHNL_VF_CAP_ADV_LINK_SPEED;
+	if (test_bit(VIRTCHNL_VF_CAP_ADV_LINK_SPEED, vf->driver_caps))
+		vfres->vf_cap_flags |= BIT(VIRTCHNL_VF_CAP_ADV_LINK_SPEED);
 
-	if (vf->driver_caps & VIRTCHNL_VF_OFFLOAD_ADV_RSS_PF)
-		vfres->vf_cap_flags |= VIRTCHNL_VF_OFFLOAD_ADV_RSS_PF;
+	if (test_bit(VIRTCHNL_VF_OFFLOAD_ADV_RSS_PF, vf->driver_caps))
+		vfres->vf_cap_flags |= BIT(VIRTCHNL_VF_OFFLOAD_ADV_RSS_PF);
 
-	if (vf->driver_caps & VIRTCHNL_VF_OFFLOAD_USO)
-		vfres->vf_cap_flags |= VIRTCHNL_VF_OFFLOAD_USO;
+	if (test_bit(VIRTCHNL_VF_OFFLOAD_USO, vf->driver_caps))
+		vfres->vf_cap_flags |= BIT(VIRTCHNL_VF_OFFLOAD_USO);
 
-	if (vf->driver_caps & VIRTCHNL_VF_OFFLOAD_QOS)
-		vfres->vf_cap_flags |= VIRTCHNL_VF_OFFLOAD_QOS;
+	if (test_bit(VIRTCHNL_VF_OFFLOAD_QOS, vf->driver_caps))
+		vfres->vf_cap_flags |= BIT(VIRTCHNL_VF_OFFLOAD_QOS);
 
-	if (vf->driver_caps & VIRTCHNL_VF_CAP_PTP)
-		vfres->vf_cap_flags |= VIRTCHNL_VF_CAP_PTP;
+	if (test_bit(VIRTCHNL_VF_CAP_PTP, vf->driver_caps))
+		vfres->vf_cap_flags |= BIT(VIRTCHNL_VF_CAP_PTP);
 
 	vfres->num_vsis = 1;
 	/* Tx and Rx queue are equal for VF */
@@ -344,7 +348,8 @@ static int ice_vc_get_vf_res_msg(struct ice_vf *vf, u8 *msg)
 			vf->hw_lan_addr);
 
 	/* match guest capabilities */
-	vf->driver_caps = vfres->vf_cap_flags;
+	bitmap_from_arr32(vf->driver_caps, &vfres->vf_cap_flags,
+			  BITS_PER_TYPE(u32));
 
 	ice_vc_set_caps_allowlist(vf);
 	ice_vc_set_working_allowlist(vf);
@@ -1027,9 +1032,9 @@ static int ice_vc_del_mac_addr_msg(struct ice_vf *vf, u8 *msg)
  *
  * Return true if VIRTCHNL_VF_OFFLOAD_VLAN capability is set, else return false
  */
-static bool ice_vf_vlan_offload_ena(u32 caps)
+static bool ice_vf_vlan_offload_ena(const unsigned long *caps)
 {
-	return !!(caps & VIRTCHNL_VF_OFFLOAD_VLAN);
+	return test_bit(VIRTCHNL_VF_OFFLOAD_VLAN, caps);
 }
 
 /**
@@ -1431,7 +1436,7 @@ static int ice_vc_query_rxdid(struct ice_vf *vf)
 		goto err;
 	}
 
-	if (!(vf->driver_caps & VIRTCHNL_VF_OFFLOAD_RX_FLEX_DESC)) {
+	if (!test_bit(VIRTCHNL_VF_OFFLOAD_RX_FLEX_DESC, vf->driver_caps)) {
 		v_ret = VIRTCHNL_STATUS_ERR_PARAM;
 		goto err;
 	}
-- 
2.49.0


^ permalink raw reply related

* [PATCH net v4] ipv6: Implement limits on extension header parsing
From: Daniel Borkmann @ 2026-04-28 15:37 UTC (permalink / raw)
  To: kuba
  Cc: edumazet, dsahern, tom, willemdebruijn.kernel, idosch, pabeni,
	justin.iurman, netdev

ipv6_{skip_exthdr,find_hdr}() and ip6_{tnl_parse_tlv_enc_lim,
protocol_deliver_rcu}() iterate over IPv6 extension headers until they
find a non-extension-header protocol or run out of packet data. The
loops have no iteration counter, relying solely on the packet length
to bound them. For a crafted packet with 8-byte extension headers
filling a 64KB jumbogram, this means a worst case of up to ~8k
iterations with a skb_header_pointer call each. ipv6_skip_exthdr(),
for example, is used where it parses the inner quoted packet inside
an incoming ICMPv6 error:

  - icmpv6_rcv
    - checksum validation
    - case ICMPV6_DEST_UNREACH
      - icmpv6_notify
        - pskb_may_pull()       <- pull inner IPv6 header
        - ipv6_skip_exthdr()    <- iterates here
        - pskb_may_pull()
        - ipprot->err_handler() <- sk lookup

The per-iteration cost of ipv6_skip_exthdr itself is generally
light, but skb_header_pointer becomes more costly on reassembled
packets: the first ~1232 bytes of the inner packet are in the skb's
linear area, but the remaining ~63KB are in the frag_list where
skb_copy_bits is needed to read data.

Initially, the idea was to add a configurable limit via a new
sysctl knob with default 8, in line with knobs from commit
47d3d7ac656a ("ipv6: Implement limits on Hop-by-Hop and Destination
options"), but two reasons eventually argued against it:

- It adds to UAPI that needs to be maintained forever, and
  upcoming work is restricting extension header ordering anyway,
  leaving little reason for another sysctl knob
- exthdrs_core.c is always built-in even when CONFIG_IPV6=n,
  where struct net has no .ipv6 member, so the read site would
  need an ifdef'd fallback to a constant anyway

Therefore, just use a constant (IP6_MAX_EXT_HDRS_CNT). All four
extension header walking functions are now bound by this limit.

Note that the check in ip6_protocol_deliver_rcu() happens right
before the goto resubmit, such that we don't have to have a test
for ipv6_ext_hdr() in the fast-path.

There's an ongoing IETF draft-iurman-6man-eh-occurrences to enforce
IPv6 extension headers ordering and occurrence. The latter also
discusses security implications. As per RFC8200 section 4.1, the
occurrence rules for extension headers provide a practical upper
bound which is 8. In order to be conservative, let's define
IP6_MAX_EXT_HDRS_CNT as 4x that to leave enough room for quirky
setups. In the unlikely event that this is still not enough, then
we might need to reconsider a sysctl.

Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
---
 v3->v4:
   - Switching to hard-coded define with 32 limit as discussed
     earlier, otherwise fixing the kbuild-bot error becomes
     to ugly.. was worth a try, but given the case of being
     built-in also with CONFIG_IPV6=n define seems better
     (Justin, Victor, kbuild-bot)
 v2->v3:
   - Adding IP6SKB_HOPBYHOP coverage (Justin)
   - I left the limit at 8 w/ sysctl, still feels the better
     option to me if we can keep the worst-case more tightened
 v1->v2:
   - Set the default to 8 (Justin)
   - Update IETF references (Justin)
   - Add core path coverage as well (Justin)

 include/net/dropreason-core.h | 6 ++++++
 include/net/ipv6.h            | 3 +++
 net/ipv6/exthdrs_core.c       | 7 +++++++
 net/ipv6/ip6_input.c          | 5 +++++
 net/ipv6/ip6_tunnel.c         | 4 ++++
 5 files changed, 25 insertions(+)

diff --git a/include/net/dropreason-core.h b/include/net/dropreason-core.h
index e0ca3904ff8e..2f312d1f67d6 100644
--- a/include/net/dropreason-core.h
+++ b/include/net/dropreason-core.h
@@ -99,6 +99,7 @@
 	FN(FRAG_TOO_FAR)		\
 	FN(TCP_MINTTL)			\
 	FN(IPV6_BAD_EXTHDR)		\
+	FN(IPV6_TOO_MANY_EXTHDRS)	\
 	FN(IPV6_NDISC_FRAG)		\
 	FN(IPV6_NDISC_HOP_LIMIT)	\
 	FN(IPV6_NDISC_BAD_CODE)		\
@@ -494,6 +495,11 @@ enum skb_drop_reason {
 	SKB_DROP_REASON_TCP_MINTTL,
 	/** @SKB_DROP_REASON_IPV6_BAD_EXTHDR: Bad IPv6 extension header. */
 	SKB_DROP_REASON_IPV6_BAD_EXTHDR,
+	/**
+	 * @SKB_DROP_REASON_IPV6_TOO_MANY_EXTHDRS: Number of IPv6 extension
+	 * headers in the packet exceeds IP6_MAX_EXT_HDRS_CNT.
+	 */
+	SKB_DROP_REASON_IPV6_TOO_MANY_EXTHDRS,
 	/** @SKB_DROP_REASON_IPV6_NDISC_FRAG: invalid frag (suppress_frag_ndisc). */
 	SKB_DROP_REASON_IPV6_NDISC_FRAG,
 	/** @SKB_DROP_REASON_IPV6_NDISC_HOP_LIMIT: invalid hop limit. */
diff --git a/include/net/ipv6.h b/include/net/ipv6.h
index d042afe7a245..118a24ac9e73 100644
--- a/include/net/ipv6.h
+++ b/include/net/ipv6.h
@@ -90,6 +90,9 @@ struct ip_tunnel_info;
 #define IP6_DEFAULT_MAX_DST_OPTS_LEN	 INT_MAX /* No limit */
 #define IP6_DEFAULT_MAX_HBH_OPTS_LEN	 INT_MAX /* No limit */
 
+/* Hard limit on traversed IPv6 extension headers */
+#define IP6_MAX_EXT_HDRS_CNT		 32
+
 /*
  *	Addr type
  *	
diff --git a/net/ipv6/exthdrs_core.c b/net/ipv6/exthdrs_core.c
index 49e31e4ae7b7..9d06d487e8b1 100644
--- a/net/ipv6/exthdrs_core.c
+++ b/net/ipv6/exthdrs_core.c
@@ -73,6 +73,7 @@ int ipv6_skip_exthdr(const struct sk_buff *skb, int start, u8 *nexthdrp,
 		     __be16 *frag_offp)
 {
 	u8 nexthdr = *nexthdrp;
+	int exthdr_cnt = 0;
 
 	*frag_offp = 0;
 
@@ -82,6 +83,8 @@ int ipv6_skip_exthdr(const struct sk_buff *skb, int start, u8 *nexthdrp,
 
 		if (nexthdr == NEXTHDR_NONE)
 			return -1;
+		if (unlikely(exthdr_cnt++ >= IP6_MAX_EXT_HDRS_CNT))
+			return -1;
 		hp = skb_header_pointer(skb, start, sizeof(_hdr), &_hdr);
 		if (!hp)
 			return -1;
@@ -190,6 +193,7 @@ int ipv6_find_hdr(const struct sk_buff *skb, unsigned int *offset,
 {
 	unsigned int start = skb_network_offset(skb) + sizeof(struct ipv6hdr);
 	u8 nexthdr = ipv6_hdr(skb)->nexthdr;
+	int exthdr_cnt = 0;
 	bool found;
 
 	if (fragoff)
@@ -216,6 +220,9 @@ int ipv6_find_hdr(const struct sk_buff *skb, unsigned int *offset,
 			return -ENOENT;
 		}
 
+		if (unlikely(exthdr_cnt++ >= IP6_MAX_EXT_HDRS_CNT))
+			return -EBADMSG;
+
 		hp = skb_header_pointer(skb, start, sizeof(_hdr), &_hdr);
 		if (!hp)
 			return -EBADMSG;
diff --git a/net/ipv6/ip6_input.c b/net/ipv6/ip6_input.c
index 967b07aeb683..8972863c93ee 100644
--- a/net/ipv6/ip6_input.c
+++ b/net/ipv6/ip6_input.c
@@ -403,6 +403,7 @@ INDIRECT_CALLABLE_DECLARE(int tcp_v6_rcv(struct sk_buff *));
 void ip6_protocol_deliver_rcu(struct net *net, struct sk_buff *skb, int nexthdr,
 			      bool have_final)
 {
+	int exthdr_cnt = IP6CB(skb)->flags & IP6SKB_HOPBYHOP ? 1 : 0;
 	const struct inet6_protocol *ipprot;
 	struct inet6_dev *idev;
 	unsigned int nhoff;
@@ -487,6 +488,10 @@ void ip6_protocol_deliver_rcu(struct net *net, struct sk_buff *skb, int nexthdr,
 				nexthdr = ret;
 				goto resubmit_final;
 			} else {
+				if (unlikely(exthdr_cnt++ >= IP6_MAX_EXT_HDRS_CNT)) {
+					SKB_DR_SET(reason, IPV6_TOO_MANY_EXTHDRS);
+					goto discard;
+				}
 				goto resubmit;
 			}
 		} else if (ret == 0) {
diff --git a/net/ipv6/ip6_tunnel.c b/net/ipv6/ip6_tunnel.c
index c468c83af0f2..9d1037ac082f 100644
--- a/net/ipv6/ip6_tunnel.c
+++ b/net/ipv6/ip6_tunnel.c
@@ -399,11 +399,15 @@ __u16 ip6_tnl_parse_tlv_enc_lim(struct sk_buff *skb, __u8 *raw)
 	unsigned int nhoff = raw - skb->data;
 	unsigned int off = nhoff + sizeof(*ipv6h);
 	u8 nexthdr = ipv6h->nexthdr;
+	int exthdr_cnt = 0;
 
 	while (ipv6_ext_hdr(nexthdr) && nexthdr != NEXTHDR_NONE) {
 		struct ipv6_opt_hdr *hdr;
 		u16 optlen;
 
+		if (unlikely(exthdr_cnt++ >= IP6_MAX_EXT_HDRS_CNT))
+			break;
+
 		if (!pskb_may_pull(skb, off + sizeof(*hdr)))
 			break;
 
-- 
2.43.0


^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox