* Re: [PATCH net v1 2/3] hinic3: fix use-after-free on DMA mapping failure
From: Fan Gong @ 2026-07-20 13:36 UTC (permalink / raw)
To: xuanqiang.luo
Cc: andrew+netdev, davem, edumazet, gongfan1, guoxin09, gur.stavi,
kuba, linux-kernel, luoxuanqiang, netdev, pabeni, stable, wudi234,
tengpeisen, shijing34, wulike1, chiqijun, ducheng11
In-Reply-To: <20260710090527.58354-3-xuanqiang.luo@linux.dev>
> drivers/net/ethernet/huawei/hinic3/hinic3_tx.c | 5 +++--
> 1 file changed, 3 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_tx.c b/drivers/net/ethernet/huawei/hinic3/hinic3_tx.c
> index 9306bf0020caf..5739ecb08d0d3 100644
> --- a/drivers/net/ethernet/huawei/hinic3/hinic3_tx.c
> +++ b/drivers/net/ethernet/huawei/hinic3/hinic3_tx.c
> @@ -578,8 +578,6 @@ static netdev_tx_t hinic3_send_one_skb(struct sk_buff *skb,
> *wqe_combo.task = task;
>
> tx_info = &txq->tx_info[pi];
> - tx_info->skb = skb;
> - tx_info->wqebb_cnt = wqebb_cnt;
>
> err = hinic3_tx_map_skb(netdev, skb, txq, tx_info, &wqe_combo);
> if (err) {
> @@ -589,6 +587,9 @@ static netdev_tx_t hinic3_send_one_skb(struct sk_buff *skb,
> goto err_drop_pkt;
> }
>
> + tx_info->skb = skb;
> + tx_info->wqebb_cnt = wqebb_cnt;
> +
> netif_subqueue_sent(netdev, txq->sq->q_id, skb->len);
> netif_subqueue_maybe_stop(netdev, txq->sq->q_id,
> hinic3_wq_free_wqebbs(&txq->sq->wq),
Thanks for your fixes, the proposed modification of the hinic3 driver has been
tested on hardware, and it works fine.
Reviewed-by: Fan Gong <gongfan1@huawei.com>
^ permalink raw reply
* [syzbot ci] Re: sctp: socket: refactor sctp_skb_recv_datagram to use ERR_PTR
From: syzbot ci @ 2026-07-20 13:33 UTC (permalink / raw)
To: davem, edumazet, horms, kuba, l1138897701, linux-kernel,
linux-sctp, lucien.xin, marcelo.leitner, netdev, pabeni
Cc: syzbot, syzkaller-bugs
In-Reply-To: <20260720082145.1072547-1-l1138897701@163.com>
syzbot ci has tested the following series
[v4] sctp: socket: refactor sctp_skb_recv_datagram to use ERR_PTR
https://lore.kernel.org/all/20260720082145.1072547-1-l1138897701@163.com
* [PATCH net v4] sctp: socket: refactor sctp_skb_recv_datagram to use ERR_PTR
and found the following issue:
general protection fault in sctp_recvmsg
Full report is available here:
https://ci.syzbot.org/series/f979ded9-9743-486d-b911-69a904c9f4ad
***
general protection fault in sctp_recvmsg
tree: net
URL: https://kernel.googlesource.com/pub/scm/linux/kernel/git/netdev/net.git
base: e13caf1c26587434f0b768193100440939c0fb91
arch: amd64
compiler: Debian clang version 22.1.6 (++20260514074242+fc4aad7b5db3-1~exp1~20260514074407.73), Debian LLD 22.1.6
config: https://ci.syzbot.org/builds/e4b39daf-f862-498e-8aeb-c22b5a168551/config
syz repro: https://ci.syzbot.org/findings/1893a3b4-56e4-49d0-9865-4ff919f027b3/syz_repro
Oops: general protection fault, probably for non-canonical address 0xdffffc000000000e: 0000 [#1] SMP KASAN PTI
KASAN: null-ptr-deref in range [0x0000000000000070-0x0000000000000077]
CPU: 0 UID: 0 PID: 5830 Comm: syz.2.19 Not tainted syzkaller #0 PREEMPT(full)
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.2-debian-1.16.2-1 04/01/2014
RIP: 0010:sctp_recvmsg+0x28d/0xd60 net/sctp/socket.c:2135
Code: ff 72 0d e8 35 6a d6 f6 41 89 ee e9 da 06 00 00 4c 89 7c 24 08 48 8d 7d 70 48 89 f8 48 c1 e8 03 48 b9 00 00 00 00 00 fc ff df <0f> b6 04 08 84 c0 0f 85 2f 08 00 00 4c 63 7d 70 4d 39 fe 4d 0f 43
RSP: 0018:ffffc90003a8f838 EFLAGS: 00010202
RAX: 000000000000000e RBX: ffff888115ad1c80 RCX: dffffc0000000000
RDX: 0000000000000000 RSI: 0000000000000001 RDI: 0000000000000070
RBP: 0000000000000000 R08: 1ffff92000751ef4 R09: 1ffff11022b5a460
R10: dffffc0000000000 R11: fffffbfff206639f R12: ffff888115ad22f0
R13: ffffc90003a8fd00 R14: 0000000000000000 R15: 0000000000000300
FS: 00007facf6b766c0(0000) GS:ffff88818dc17000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00002000000055c0 CR3: 000000010a812000 CR4: 00000000000006f0
Call Trace:
<TASK>
sock_recvmsg_nosec net/socket.c:1128 [inline]
sock_recvmsg+0x166/0x1e0 net/socket.c:1148
____sys_recvmsg+0x1e6/0x4a0 net/socket.c:2904
___sys_recvmsg+0x213/0x5a0 net/socket.c:2946
do_recvmmsg+0x31a/0x7f0 net/socket.c:3041
__sys_recvmmsg net/socket.c:3115 [inline]
__do_sys_recvmmsg net/socket.c:3138 [inline]
__se_sys_recvmmsg net/socket.c:3131 [inline]
__x64_sys_recvmmsg+0x198/0x250 net/socket.c:3131
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7facf5d9ce59
Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007facf6b76028 EFLAGS: 00000246 ORIG_RAX: 000000000000012b
RAX: ffffffffffffffda RBX: 00007facf6015fa0 RCX: 00007facf5d9ce59
RDX: 000000000400023c RSI: 00002000000055c0 RDI: 0000000000000004
RBP: 00007facf5e32e6f R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000300 R11: 0000000000000246 R12: 0000000000000000
R13: 00007facf6016038 R14: 00007facf6015fa0 R15: 00007ffcc05d60d8
</TASK>
Modules linked in:
---[ end trace 0000000000000000 ]---
RIP: 0010:sctp_recvmsg+0x28d/0xd60 net/sctp/socket.c:2135
Code: ff 72 0d e8 35 6a d6 f6 41 89 ee e9 da 06 00 00 4c 89 7c 24 08 48 8d 7d 70 48 89 f8 48 c1 e8 03 48 b9 00 00 00 00 00 fc ff df <0f> b6 04 08 84 c0 0f 85 2f 08 00 00 4c 63 7d 70 4d 39 fe 4d 0f 43
RSP: 0018:ffffc90003a8f838 EFLAGS: 00010202
RAX: 000000000000000e RBX: ffff888115ad1c80 RCX: dffffc0000000000
RDX: 0000000000000000 RSI: 0000000000000001 RDI: 0000000000000070
RBP: 0000000000000000 R08: 1ffff92000751ef4 R09: 1ffff11022b5a460
R10: dffffc0000000000 R11: fffffbfff206639f R12: ffff888115ad22f0
R13: ffffc90003a8fd00 R14: 0000000000000000 R15: 0000000000000300
FS: 00007facf6b766c0(0000) GS:ffff88818dc17000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00002000000055c0 CR3: 000000010a812000 CR4: 00000000000006f0
----------------
Code disassembly (best guess):
0: ff 72 0d push 0xd(%rdx)
3: e8 35 6a d6 f6 call 0xf6d66a3d
8: 41 89 ee mov %ebp,%r14d
b: e9 da 06 00 00 jmp 0x6ea
10: 4c 89 7c 24 08 mov %r15,0x8(%rsp)
15: 48 8d 7d 70 lea 0x70(%rbp),%rdi
19: 48 89 f8 mov %rdi,%rax
1c: 48 c1 e8 03 shr $0x3,%rax
20: 48 b9 00 00 00 00 00 movabs $0xdffffc0000000000,%rcx
27: fc ff df
* 2a: 0f b6 04 08 movzbl (%rax,%rcx,1),%eax <-- trapping instruction
2e: 84 c0 test %al,%al
30: 0f 85 2f 08 00 00 jne 0x865
36: 4c 63 7d 70 movslq 0x70(%rbp),%r15
3a: 4d 39 fe cmp %r15,%r14
3d: 4d rex.WRB
3e: 0f .byte 0xf
3f: 43 rex.XB
***
If these findings have caused you to resend the series or submit a
separate fix, please add the following tag to your commit message:
Tested-by: syzbot@syzkaller.appspotmail.com
---
This report is generated by a bot. It may contain errors.
syzbot ci engineers can be reached at syzkaller@googlegroups.com.
To test a patch for this bug, please reply with `#syz test`
(should be on a separate line).
The patch should be attached to the email.
Note: arguments like custom git repos and branches are not supported.
^ permalink raw reply
* Re: [PATCH v5] r8169: migrate Rx path to page_pool, prepare for XDP
From: Simon Horman @ 2026-07-20 13:30 UTC (permalink / raw)
To: atharvapotdar07
Cc: Simon Horman, hkallweit1, nic_swsd, andrew+netdev, davem,
edumazet, kuba, pabeni, romieu, netdev
In-Reply-To: <20260713035049.25310-1-atharvapotdar07@gmail.com>
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
https://sashiko.dev/#/patchset/20260713035049.25310-1-atharvapotdar07@gmail.com?part=1
---
--- Patch [1]: [PATCH v5] r8169: migrate Rx path to page_pool, prepare for XDP ---
commit db5a368e94d087883d45d81ba30f59c05de881cf
Author: Atharva Potdar <atharvapotdar07@gmail.com>
r8169: migrate Rx path to page_pool, prepare for XDP
Replace the alloc_pages() + dma_map_page() + skb_copy() Rx model with
a page_pool-backed napi_build_skb() path for zero-copy delivery.
> diff --git a/drivers/net/ethernet/realtek/r8169_main.c b/drivers/net/ethernet/realtek/r8169_main.c
> index ec4fc21fa21fb..c548e7e8db874 100644
> --- a/drivers/net/ethernet/realtek/r8169_main.c
> +++ b/drivers/net/ethernet/realtek/r8169_main.c
> @@ -4311,8 +4363,20 @@ static void rtl_reset_work(struct rtl8169_private *tp)
>
> rtl8169_cleanup(tp);
>
> - for (i = 0; i < NUM_RX_DESC; i++)
> - rtl8169_mark_to_asic(tp->RxDescArray + i);
> + for (i = 0; i < NUM_RX_DESC; i++) {
> + if (tp->Rx_databuff[i])
> + continue;
> + tp->Rx_databuff[i] =
> + rtl8169_alloc_rx_data(tp, tp->RxDescArray + i);
> + if (!tp->Rx_databuff[i])
> + break;
> + }
[Severity: High]
If memory allocation fails under pressure during an interface reset,
this loop breaks and leaves tp->Rx_databuff[i] as NULL. When NAPI runs,
could this lead to a permanent deadlock in rtl_rx()?
If a descriptor has a NULL buffer, rtl_rx() currently does:
if (unlikely(!tp->Rx_databuff[entry]))
break;
This breaks out of the Rx loop without advancing tp->cur_rx. Because
cur_rx didn't advance, the subsequent call to rtl8169_rx_refill() sees
tp->dirty_rx == tp->cur_rx and will not attempt to allocate memory.
Would it be better to advance cur_rx and drop the packet, or handle
the allocation failure without stalling the entire ring?
> @@ -4820,21 +4907,40 @@ static int rtl_rx(struct net_device *dev, struct rtl8169_private *tp, int budget
> goto release_descriptor;
> }
>
> - skb = napi_alloc_skb(&tp->napi, pkt_size);
> - if (unlikely(!skb)) {
> - dev->stats.rx_dropped++;
> - goto release_descriptor;
> - }
> + if (unlikely(!tp->Rx_databuff[entry]))
> + break;
>
> addr = le64_to_cpu(desc->addr);
> rx_buf = page_address(tp->Rx_databuff[entry]);
>
> dma_sync_single_for_cpu(d, addr, pkt_size, DMA_FROM_DEVICE);
> - prefetch(rx_buf);
> - skb_copy_to_linear_data(skb, rx_buf, pkt_size);
> - skb->tail += pkt_size;
> - skb->len = pkt_size;
> - dma_sync_single_for_device(d, addr, pkt_size, DMA_FROM_DEVICE);
> + prefetch(rx_buf + tp->rx_headroom);
> +
> + if (unlikely(tp->rx_headroom == 0)) {
> + skb = napi_alloc_skb(&tp->napi, pkt_size);
> + if (likely(skb)) {
> + skb_copy_to_linear_data(skb, rx_buf, pkt_size);
> + skb_put(skb, pkt_size);
[Severity: Critical]
This is a pre-existing issue, but earlier in rtl_rx() there is an
integer underflow on runt frames:
pkt_size = status & GENMASK(13, 0);
if (likely(!(dev->features & NETIF_F_RXFCS)))
pkt_size -= ETH_FCS_LEN;
If NETIF_F_RXALL is enabled, the hardware can pass runt frames smaller
than 4 bytes. In this case, pkt_size underflows to a massive unsigned
value.
Since this code passes pkt_size directly into dma_sync_single_for_device()
and skb_put(), could this massive value trigger an skb_over_panic() and
crash the kernel when processing runts?
^ permalink raw reply
* Re: [PATCH v5] r8169: migrate Rx path to page_pool, prepare for XDP
From: Simon Horman @ 2026-07-20 13:29 UTC (permalink / raw)
To: atharvapotdar07
Cc: Simon Horman, hkallweit1, nic_swsd, andrew+netdev, davem,
edumazet, kuba, pabeni, romieu, netdev
In-Reply-To: <20260713035049.25310-1-atharvapotdar07@gmail.com>
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260713035049.25310-1-atharvapotdar07@gmail.com?part=1
---
r8169: migrate Rx path to page_pool, prepare for XDP
This commit replaces the alloc_pages() + dma_map_page() + skb_copy() Rx
model with a page_pool-backed napi_build_skb() path. It keeps MACs up to
RTL_GIGA_MAC_VER_06 on the copy path, and reserves XDP_PACKET_HEADROOM
on newer MACs. It also decouples cur_rx from dirty_rx and registers an
xdp_rxq for MEM_TYPE_PAGE_POOL.
A few questions on the new code below.
> diff --git a/drivers/net/ethernet/realtek/r8169_main.c b/drivers/net/ethernet/realtek/r8169_main.c
> index ec4fc21fa21f..c548e7e8db87 100644
> --- a/drivers/net/ethernet/realtek/r8169_main.c
> +++ b/drivers/net/ethernet/realtek/r8169_main.c
[ ... ]
> @@ -4221,12 +4228,57 @@ static int rtl8169_rx_fill(struct rtl8169_private *tp)
>
> static int rtl8169_init_ring(struct rtl8169_private *tp)
> {
> + struct page_pool_params params = {0};
> + int err;
> +
> rtl8169_init_ring_indexes(tp);
>
> + if (tp->mac_version <= RTL_GIGA_MAC_VER_06) {
> + tp->rx_headroom = 0;
> + tp->rx_buf_sz = R8169_RX_BUF_SIZE;
> + } else {
> + tp->rx_headroom = XDP_PACKET_HEADROOM;
> + tp->rx_buf_sz = SZ_16K -
> + SKB_DATA_ALIGN(sizeof(struct skb_shared_info)) -
> + XDP_PACKET_HEADROOM;
> + }
[High]
Can this silently break jumbo Rx on RTL8125/RTL8126? For those MACs
rtl_jumbo_max() returns JUMBO_16K (16362 bytes) and dev->max_mtu is
left unchanged by this patch, but tp->rx_buf_sz here becomes:
SZ_16K - SKB_DATA_ALIGN(sizeof(struct skb_shared_info))
- XDP_PACKET_HEADROOM
which is roughly 15808 bytes. This value is written into every
descriptor by rtl8169_mark_to_asic():
WRITE_ONCE(desc->opts1, cpu_to_le32(DescOwn | eor | rx_buf_sz));
while rtl_set_rx_max_size() still programs RxMaxSize based on
R8169_RX_BUF_SIZE + 1 = 16384. When a frame larger than ~15808 bytes
arrives at an MTU near 16362, the hardware will split it across
descriptors, and rtl8169_fragmented_frame() then drops it and bumps
rx_length_errors:
if (unlikely(rtl8169_fragmented_frame(status))) {
dev->stats.rx_dropped++;
dev->stats.rx_length_errors++;
goto release_descriptor;
}
Should dev->max_mtu (or rtl_jumbo_max()) be adjusted for the newer
MACs so this reduction in the per-descriptor buffer size does not
silently drop max-size jumbo frames?
Relatedly, rtl8169_change_mtu() does not recompute rx_buf_sz or
repopulate the ring, so a change from a smaller MTU up towards
JUMBO_16K will not re-evaluate this either. Is that intentional?
Since the commit message describes this only as XDP preparation, could
the MTU-visible behaviour change be called out there as well?
[ ... ]
> + tp->rx_pool = page_pool_create(¶ms);
> + if (IS_ERR(tp->rx_pool)) {
> + err = PTR_ERR(tp->rx_pool);
> + tp->rx_pool = NULL;
> + return err;
> + }
> +
> + err = xdp_rxq_info_reg(&tp->xdp_rxq, tp->dev, 0, tp->napi.napi_id);
> + if (err)
> + goto err_free_pool;
[Medium]
At this point, is tp->napi.napi_id actually assigned yet? rtl_open()
calls rtl8169_init_ring() before rtl8169_up() -> napi_enable(), and
napi_hash_add() (which populates napi_id) runs from
napi_enable_locked(). netif_napi_add() at probe time does not assign a
napi_id either.
If so, xdp_rxq_info_reg() is called with napi_id == 0 and the xdp_rxq
loses the association intended for busy-poll, netlink queue
introspection and XDP redirect wakeups.
Would deferring xdp_rxq_info_reg() until after napi_enable(), or using
a persistent napi_config-based id, keep the association the commit
message is establishing?
[ ... ]
> @@ -4311,8 +4363,20 @@ static void rtl_reset_work(struct rtl8169_private *tp)
>
> rtl8169_cleanup(tp);
>
> - for (i = 0; i < NUM_RX_DESC; i++)
> - rtl8169_mark_to_asic(tp->RxDescArray + i);
> + for (i = 0; i < NUM_RX_DESC; i++) {
> + if (tp->Rx_databuff[i])
> + continue;
> + tp->Rx_databuff[i] =
> + rtl8169_alloc_rx_data(tp, tp->RxDescArray + i);
> + if (!tp->Rx_databuff[i])
> + break;
> + }
> +
> + for (i = 0; i < NUM_RX_DESC; i++) {
> + if (!tp->Rx_databuff[i])
> + continue;
> + rtl8169_mark_to_asic(tp->RxDescArray + i, tp->rx_buf_sz);
> + }
>
> napi_enable(&tp->napi);
> rtl_hw_start(tp);
[High]
Can this leave the Rx ring with permanent gaps under memory pressure?
rtl8169_cleanup() does not clear Rx_databuff[], so on entry this loop
tries to refill slots consumed by the zero-copy path. On the first
allocation failure it breaks:
if (!tp->Rx_databuff[i])
break;
This skips every later NULL slot, even those where a subsequent
allocation might have succeeded. The second loop only marks entries
that already have a databuff, so the gap entries keep DescOwn=0.
rtl_reset_work() is void and rtl_hw_start() runs unconditionally, so
hardware then walks the ring sequentially and stalls at the first
gap. Since the normal refill (rtl8169_rx_refill()) only iterates
between dirty_rx and cur_rx, would slots at higher indices that were
never allocated here ever be filled again?
Would it be preferable to keep retrying across all slots (rather than
break on the first failure), or to fail the reset out to a scheduled
retry when allocation fails?
[ ... ]
> @@ -4768,16 +4832,39 @@ static inline void rtl8169_rx_csum(struct sk_buff *skb, u32 opts1)
> skb_checksum_none_assert(skb);
> }
>
> +static void rtl8169_rx_refill(struct rtl8169_private *tp)
> +{
> + while (tp->dirty_rx != tp->cur_rx) {
> + unsigned int entry = tp->dirty_rx % NUM_RX_DESC;
> + struct RxDesc *desc = tp->RxDescArray + entry;
> +
> + if (!tp->Rx_databuff[entry]) {
> + struct page *new_page = page_pool_dev_alloc_pages(tp->rx_pool);
> +
> + if (unlikely(!new_page))
> + break;
> +
> + tp->Rx_databuff[entry] = new_page;
> +
> + desc->addr = cpu_to_le64(page_pool_get_dma_addr(new_page) +
> + tp->rx_headroom);
> + }
> + rtl8169_mark_to_asic(desc, tp->rx_buf_sz);
> +
> + tp->dirty_rx++;
> + }
> +}
> +
> static int rtl_rx(struct net_device *dev, struct rtl8169_private *tp, int budget)
> {
> struct device *d = tp_to_dev(tp);
> int count;
>
> - for (count = 0; count < budget; count++, tp->cur_rx++) {
> + for (count = 0; count < budget;) {
> unsigned int pkt_size, entry = tp->cur_rx % NUM_RX_DESC;
> struct RxDesc *desc = tp->RxDescArray + entry;
> struct sk_buff *skb;
> - const void *rx_buf;
> + void *rx_buf;
> dma_addr_t addr;
> u32 status;
>
> @@ -4820,21 +4907,40 @@ static int rtl_rx(struct net_device *dev, struct rtl8169_private *tp, int budget
> goto release_descriptor;
> }
>
> - skb = napi_alloc_skb(&tp->napi, pkt_size);
> - if (unlikely(!skb)) {
> - dev->stats.rx_dropped++;
> - goto release_descriptor;
> - }
> + if (unlikely(!tp->Rx_databuff[entry]))
> + break;
[High]
Can this pair of paths leave the ring wedged with hardware waiting on a
DescOwn=0 slot that software has already given up on?
If rtl8169_rx_refill() runs after some pages were consumed by the
zero-copy path and page_pool_dev_alloc_pages() fails, the loop breaks
without calling rtl8169_mark_to_asic() and without advancing dirty_rx:
if (unlikely(!new_page))
break;
...
rtl8169_mark_to_asic(desc, tp->rx_buf_sz);
tp->dirty_rx++;
So the descriptor stays with DescOwn=0 and Rx_databuff[entry] == NULL.
On the next NAPI iteration, when rtl_rx() reaches that entry the new
early exit:
if (unlikely(!tp->Rx_databuff[entry]))
break;
leaves rtl_rx() without advancing tp->cur_rx and without re-marking
the descriptor. Hardware is also stuck at this same slot, so if there
is no other IRQ source (e.g. TX completions) to schedule NAPI again,
does anything actually recover the Rx queue?
The pre-patch code always called rtl8169_mark_to_asic(desc) on the
drop path in rtl_rx(), so this stalled state was not structurally
reachable. Should the alloc-failure path here retain ownership with
the hardware (mark_to_asic with a placeholder / retry later) rather
than leaving DescOwn=0?
[ ... ]
^ permalink raw reply
* Re: [PATCH net 1/1] openvswitch: Fix CT limit teardown use-after-free
From: Andrew Lunn @ 2026-07-20 13:29 UTC (permalink / raw)
To: Yuan Tan
Cc: Ren Wei, xuyuqiabc, netdev, dev, aconole, echaudro, i.maximets,
davem, edumazet, pabeni, horms, pshelar, yihung.wei, tonanli66
In-Reply-To: <0c037f98-f219-4851-9a06-e83b25ca0f7e@gmail.com>
On Sun, Jul 19, 2026 at 11:54:31PM -0700, Yuan Tan wrote:
>
> On 7/19/26 19:52, Andrew Lunn wrote:
> > On Mon, Jul 20, 2026 at 10:14:16AM +0800, Ren Wei wrote:
> >> From: Yuqi Xu <xuyuqiabc@gmail.com>
> >>
> >> Packet processing uses CT limit state under RCU, while netns teardown
> >> frees that state under ovs_mutex. The CT limit pointer was neither removed
> >> from readers nor protected by a grace period, allowing packet processing to
> >> dereference the freed state.
> >>
> >> Replace the pointer before freeing the CT limit state. Wait for in-flight
> >> RCU readers before freeing its contents. Serialize CT limit netlink
> >> operations with teardown for the full lifetime of their state accesses.
> >>
> >> Fixes: 11efd5cb04a1 ("openvswitch: Support conntrack zone limit")
> >> Cc: stable@vger.kernel.org
> >> Reported-by: Vega <vega@nebusec.ai>
> > Is Vega a person?
>
> Hi Andrew,
>
> Thank you very much for your review!
> For context, we had previously understood that using the tool name in
> the Reported-by tag was acceptable, based on examples such as
> Reported-by: AutonomousCodeSecurity@microsoft.com and Reported-by:
> Anthropic.
>
> https://lore.kernel.org/all/20260630171016.11c02dec@kernel.org/
https://docs.kernel.org/process/submitting-patches.html
The Reported-by tag gives credit to people who find bugs and report
them and it hopefully inspires them to help us again in the
future. The tag is intended for bugs; please do not use it to credit
feature requests. The tag should be followed by a Closes: tag
pointing to the report, unless the report is not available on the
web.
If you believe this is out of date, please submit a patch with new
text to this document.
I find it valuable being a person. It indicate somebody is bothered by
the problem you are fixing. We see a lot theoretical bug fixes, which
in practice nobody ever hit. I would prefer to spend my time reviewing
real issues, not theoretical issues, and the Reported-by: is a quick
indicator of this.
> >> Assisted-by: Codex:GPT-5.4
> >> Co-developed-by: Nan Li <tonanli66@gmail.com>
> >> Signed-off-by: Nan Li <tonanli66@gmail.com>
> >> Signed-off-by: Yuqi Xu <xuyuqiabc@gmail.com>
> >> Reviewed-by: Ren Wei <enjou1224z@gmail.com>
> > Please take a look at
> > https://docs.kernel.org/process/submitting-patches.html#sign-your-work-the-developer-s-certificate-of-origin
> > and the sections that follow. What is listed here does not follow the
> > rules.
After reading this document, did you spot the second issues?
Andrew
^ permalink raw reply
* Re: [PATCH net] nfc: fdp: bound the device-supplied read size in fdp_nci_i2c_read()
From: Doruk Tan Ozturk @ 2026-07-20 13:21 UTC (permalink / raw)
To: david, horms; +Cc: oe-linux-nfc, hexlabsecurity, netdev, linux-kernel
In-Reply-To: <879ab436-043a-450d-87c0-70e7c6f4ff8f@ixit.cz>
On 20/07/2026, David Heidelberg wrote:
> I agree, I was looking at this patch yesterday and the current format
> feels unfavorable. Please send next revision with Simon suggestion :)
Will do. One heads-up before I resend: Bryam Vargas posted a v2 for the
same bug back in June [1] that is more complete than mine. It also
rejects len < FDP_NCI_I2C_MIN_PAYLOAD (the lower bound Simon flagged,
reachable via the u16 truncation) and fixes an skb leak in the same
loop, with a KASAN reproducer, and it carries Simon's Suggested-by. It
looks like it just stalled.
If you would rather pick that one up, it covers more than mine does.
Happy to send my (narrower) v2 with Simon's suggestion if you prefer it
instead, just let me know.
-Doruk
[1] https://lore.kernel.org/all/20260616-b4-disp-b1f8ab4c-v2-1-2d1fe5955325@proton.me/
^ permalink raw reply
* Re: [PATCH net] e1000e: Fix out-of-bounds MMIO access by validating BAR0 size
From: Pu Lehui @ 2026-07-20 13:16 UTC (permalink / raw)
To: Breno Leitao
Cc: Tony Nguyen, Przemek Kitszel, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, netdev, linux-kernel
In-Reply-To: <al4Z4_tFbyQM-ogu@gmail.com>
On 2026/7/20 20:53, Breno Leitao wrote:
> On Mon, Jul 20, 2026 at 07:56:20PM +0800, Pu Lehui wrote:
>>
>> On 2026/7/20 17:47, Breno Leitao wrote:
>>> On Wed, Jul 15, 2026 at 03:58:50AM +0000, Pu Lehui wrote:
>>>> +/* Minimum MMIO (BAR0) len, the largest offset is lower than 64K */
>>>
>>> Why "the largest" in this case?
>>
>> Hi Breno,
>>
>> Thanks for pointing that out. I meant the maximum register offset accessed
>> by the driver.
>>
>> IIUC, common e1000e NIC usually have a 128K BAR0. But since I'm not 100%
>> sure if older NIC might be smaller, I picked 64K as a safe lower limit
>> because it covers the largest register offset used in the driver's codebase.
>> If my assumption here is off, I'd really appreciate any corrections!
>>
>> And for this comment, how about the follow?
>> /* Minimum MMIO (BAR0) length to safely cover the maximum register offset
>> accessed by the driver */
>
> Thanks, Would something like this be a bit better?
>
> /*
> * Smallest BAR0 that covers every register the driver accesses
> */
>
make sense to me, will respin soon
^ permalink raw reply
* Re: [PATCH net] hinic: fix leak of ethtool RSS user configuration buffers
From: Joe Damato @ 2026-07-20 13:01 UTC (permalink / raw)
To: Chenguang Zhao
Cc: cai.huoqing, andrew+netdev, davem, edumazet, kuba, pabeni, netdev,
Chenguang Zhao
In-Reply-To: <20260720091102.1653378-1-chenguang.zhao@linux.dev>
On Mon, Jul 20, 2026 at 05:11:02PM +0800, Chenguang Zhao wrote:
> From: Chenguang Zhao <zhaochenguang@kylinos.cn>
>
> rss_indir_user and rss_hkey_user are allocated in __set_rss_rxfh() when
> the user configures RSS via ethtool, but hinic_remove() never frees them.
> free_netdev() only releases the nic_dev structure itself, so the separately
> allocated buffers are leaked on driver unload.
>
> Free both buffers in hinic_remove() after unregister_netdev().
>
> Fixes: 4fdc51bb4e92 ("hinic: add support for rss parameters with ethtool")
> Signed-off-by: Chenguang Zhao <zhaochenguang@kylinos.cn>
> ---
> drivers/net/ethernet/huawei/hinic/hinic_main.c | 3 +++
> 1 file changed, 3 insertions(+)
>
> diff --git a/drivers/net/ethernet/huawei/hinic/hinic_main.c b/drivers/net/ethernet/huawei/hinic/hinic_main.c
> index 42f4792d255b..7d9c46004bcd 100644
> --- a/drivers/net/ethernet/huawei/hinic/hinic_main.c
> +++ b/drivers/net/ethernet/huawei/hinic/hinic_main.c
> @@ -1433,6 +1433,9 @@ static void hinic_remove(struct pci_dev *pdev)
>
> hinic_free_intr_coalesce(nic_dev);
>
> + kfree(nic_dev->rss_indir_user);
> + kfree(nic_dev->rss_hkey_user);
> +
I scanned the code and ... maybe I got this wrong ... but it looks like
nothing actually uses these fields?
If you look at hinic_ethtool.c, the buffers are allocated, data is copied into
them and then they are never used. hinic_get_rxfh seems to read the state
directly from the device ?
I wonder if the correct fix is to drop the buffers and the memcpys entirely
because they seem unnecessary and unused.
^ permalink raw reply
* Re: [PATCH 3/8] iommu/fsl: use platform_device_set_fwnode()
From: Robin Murphy @ 2026-07-20 12:58 UTC (permalink / raw)
To: Bartosz Golaszewski, Greg Kroah-Hartman, Rafael J. Wysocki,
Danilo Krummrich, Madhavan Srinivasan, Michael Ellerman,
Nicholas Piggin, Christophe Leroy (CS GROUP), Andi Shyti,
Joerg Roedel (AMD), Will Deacon, Andy Shevchenko, Doug Berger,
Florian Fainelli, Broadcom internal kernel review list,
Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Ulf Hansson, Frank Li, Sascha Hauer,
Pengutronix Kernel Team, Fabio Estevam, Lee Jones,
Sebastian Hesselbarth, Srinivas Kandagatla
Cc: brgl, driver-core, linuxppc-dev, linux-kernel, linux-i2c, iommu,
netdev, linux-pm, imx, linux-arm-kernel, mfd, linux-arm-msm,
linux-sound
In-Reply-To: <20260720-pdev-set-fwnode-instead-of-of-node-v1-3-2dee93f42c54@oss.qualcomm.com>
On 20/07/2026 10:24 am, Bartosz Golaszewski wrote:
> Prefer the higher-level platform_device_set_fwnode() over the
> OF-specific platform_device_set_of_node() for dynamically allocated
> platform devices.
This is very much non-portable code specific to OF-only platforms, but
if the intention is to remove platform_device_set_of_node() again
already, then FWIW,
Acked-by: Robin Murphy <robin.murphy@arm.com>
(Although I'm slightly puzzled by the cover letter - AFAICS in -next,
platform_device_set_of_node() is itself very much a user of
platform_device_set_fwnode(), however in terms of symbol exports,
perhaps the former could now just be a static inline wrapper?)
> Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
> ---
> drivers/iommu/fsl_pamu.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/drivers/iommu/fsl_pamu.c b/drivers/iommu/fsl_pamu.c
> index c83bbc3faad56d6ee1c89b0a7f74028af02c81e9..268a1f752fbceab4fd24013aeea5df1b6982fbb1 100644
> --- a/drivers/iommu/fsl_pamu.c
> +++ b/drivers/iommu/fsl_pamu.c
> @@ -975,7 +975,7 @@ static __init int fsl_pamu_init(void)
> goto error_device_alloc;
> }
>
> - platform_device_set_of_node(pdev, np);
> + platform_device_set_fwnode(pdev, of_fwnode_handle(np));
>
> ret = pamu_domain_init();
> if (ret)
>
^ permalink raw reply
* Re: [PATCH net] e1000e: Fix out-of-bounds MMIO access by validating BAR0 size
From: Breno Leitao @ 2026-07-20 12:53 UTC (permalink / raw)
To: Pu Lehui
Cc: Tony Nguyen, Przemek Kitszel, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, netdev, linux-kernel
In-Reply-To: <ffc0a0d8-6560-4589-9570-04f40d817009@huaweicloud.com>
On Mon, Jul 20, 2026 at 07:56:20PM +0800, Pu Lehui wrote:
>
> On 2026/7/20 17:47, Breno Leitao wrote:
> > On Wed, Jul 15, 2026 at 03:58:50AM +0000, Pu Lehui wrote:
> > > +/* Minimum MMIO (BAR0) len, the largest offset is lower than 64K */
> >
> > Why "the largest" in this case?
>
> Hi Breno,
>
> Thanks for pointing that out. I meant the maximum register offset accessed
> by the driver.
>
> IIUC, common e1000e NIC usually have a 128K BAR0. But since I'm not 100%
> sure if older NIC might be smaller, I picked 64K as a safe lower limit
> because it covers the largest register offset used in the driver's codebase.
> If my assumption here is off, I'd really appreciate any corrections!
>
> And for this comment, how about the follow?
> /* Minimum MMIO (BAR0) length to safely cover the maximum register offset
> accessed by the driver */
Thanks, Would something like this be a bit better?
/*
* Smallest BAR0 that covers every register the driver accesses
*/
^ permalink raw reply
* [PATCH net-next v11 5/5] hinic3: Remove unneeded coalesce parameters
From: Fan Gong @ 2026-07-20 12:53 UTC (permalink / raw)
To: Fan Gong, Wu Di, Teng Peisen, netdev, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Andrew Lunn, Ioana Ciornei, Mohsin Bashir, Dimitri Daskalakis,
Harshitha Ramamurthy
Cc: linux-kernel, linux-doc, luosifu, Xin Guo, Zhou Shuai, Wu Like,
Shi Jing, Zheng Jiezhen, Maxime Chevallier
In-Reply-To: <cover.1784538912.git.root@localhost.localdomain>
Remove unneeded coalesce parameters in irq handling.
Co-developed-by: Wu Di <wudi234@huawei.com>
Signed-off-by: Wu Di <wudi234@huawei.com>
Co-developed-by: Teng Peisen <tengpeisen@huawei.com>
Signed-off-by: Teng Peisen <tengpeisen@huawei.com>
Signed-off-by: Fan Gong <gongfan1@huawei.com>
---
drivers/net/ethernet/huawei/hinic3/hinic3_irq.c | 6 +-----
drivers/net/ethernet/huawei/hinic3/hinic3_rx.h | 3 ---
2 files changed, 1 insertion(+), 8 deletions(-)
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_irq.c b/drivers/net/ethernet/huawei/hinic3/hinic3_irq.c
index 94d0773ebf2c..a5013dd8ade2 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_irq.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_irq.c
@@ -149,13 +149,9 @@ static int hinic3_set_interrupt_moder(struct net_device *netdev, u16 q_id,
nic_dev->intr_coalesce[q_id].resend_timer_cfg;
err = hinic3_set_interrupt_cfg(nic_dev->hwdev, info);
- if (err) {
+ if (err)
netdev_err(netdev,
"Failed to modify moderation for Queue: %u\n", q_id);
- } else {
- nic_dev->rxqs[q_id].last_coalesc_timer_cfg = coalesc_timer_cfg;
- nic_dev->rxqs[q_id].last_pending_limit = pending_limit;
- }
return err;
}
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_rx.h b/drivers/net/ethernet/huawei/hinic3/hinic3_rx.h
index c11d080408a7..2ab691ed11a9 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_rx.h
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_rx.h
@@ -111,9 +111,6 @@ struct hinic3_rxq {
dma_addr_t cqe_start_paddr;
struct dim dim;
-
- u8 last_coalesc_timer_cfg;
- u8 last_pending_limit;
} ____cacheline_aligned;
struct hinic3_dyna_rxq_res {
--
2.54.0
^ permalink raw reply related
* [PATCH net-next v11 1/5] hinic3: Add ethtool queue ops
From: Fan Gong @ 2026-07-20 12:53 UTC (permalink / raw)
To: Fan Gong, Wu Di, Teng Peisen, netdev, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Andrew Lunn, Ioana Ciornei, Mohsin Bashir, Dimitri Daskalakis,
Harshitha Ramamurthy
Cc: linux-kernel, linux-doc, luosifu, Xin Guo, Zhou Shuai, Wu Like,
Shi Jing, Zheng Jiezhen, Maxime Chevallier
In-Reply-To: <cover.1784538912.git.root@localhost.localdomain>
Implement following ethtool callback function:
.get_ringparam
.set_ringparam
These callbacks allow users to utilize ethtool for detailed
queue depth configuration and monitoring.
Change port_state_mutex to state_lock as a unified mutex lock
in hinic3_nic_dev.
Tightens the success criterion for hinic3_rx_fill_buffers() in
hinic3_configure_rxqs().
Changes hinic3_tx_csum()/hinic3_tx_offload() to propagate
skb_checksum_help() failures into a TX drop.
Renames hinic3_uninit_qps() to hinic3_get_cur_qps().
Splits hinic3_open_channel() into hinic3_prepare_channel() and
hinic3_open_channel().
Co-developed-by: Wu Di <wudi234@huawei.com>
Signed-off-by: Wu Di <wudi234@huawei.com>
Co-developed-by: Teng Peisen <tengpeisen@huawei.com>
Signed-off-by: Teng Peisen <tengpeisen@huawei.com>
Signed-off-by: Fan Gong <gongfan1@huawei.com>
---
.../ethernet/huawei/hinic3/hinic3_ethtool.c | 91 +++++++++++++++
.../net/ethernet/huawei/hinic3/hinic3_main.c | 4 +-
.../huawei/hinic3/hinic3_netdev_ops.c | 106 ++++++++++++++++--
.../ethernet/huawei/hinic3/hinic3_nic_dev.h | 11 +-
.../ethernet/huawei/hinic3/hinic3_nic_io.c | 4 +-
.../ethernet/huawei/hinic3/hinic3_nic_io.h | 8 +-
.../net/ethernet/huawei/hinic3/hinic3_rx.c | 2 +-
.../net/ethernet/huawei/hinic3/hinic3_tx.c | 7 +-
8 files changed, 216 insertions(+), 17 deletions(-)
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c b/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c
index 90fc16288de9..e6dff46a0d00 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c
@@ -9,6 +9,7 @@
#include <linux/errno.h>
#include <linux/etherdevice.h>
#include <linux/netdevice.h>
+#include <linux/netlink.h>
#include <linux/ethtool.h>
#include "hinic3_lld.h"
@@ -409,6 +410,94 @@ hinic3_get_link_ksettings(struct net_device *netdev,
return 0;
}
+static void hinic3_get_ringparam(struct net_device *netdev,
+ struct ethtool_ringparam *ring,
+ struct kernel_ethtool_ringparam *kernel_ring,
+ struct netlink_ext_ack *extack)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+
+ ring->rx_max_pending = HINIC3_MAX_RX_QUEUE_DEPTH;
+ ring->tx_max_pending = HINIC3_MAX_TX_QUEUE_DEPTH;
+ ring->rx_pending = nic_dev->q_params.rq_depth;
+ ring->tx_pending = nic_dev->q_params.sq_depth;
+}
+
+static void hinic3_update_qp_depth(struct net_device *netdev,
+ u32 sq_depth, u32 rq_depth)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ u16 i;
+
+ nic_dev->q_params.sq_depth = sq_depth;
+ nic_dev->q_params.rq_depth = rq_depth;
+ for (i = 0; i < nic_dev->max_qps; i++) {
+ nic_dev->txqs[i].q_depth = sq_depth;
+ nic_dev->txqs[i].q_mask = sq_depth - 1;
+ nic_dev->rxqs[i].q_depth = rq_depth;
+ nic_dev->rxqs[i].q_mask = rq_depth - 1;
+ }
+}
+
+static int hinic3_check_ringparam_valid(struct net_device *netdev,
+ const struct ethtool_ringparam *ring,
+ struct netlink_ext_ack *extack)
+{
+ if (ring->tx_pending < HINIC3_MIN_QUEUE_DEPTH ||
+ ring->rx_pending < HINIC3_MIN_QUEUE_DEPTH) {
+ NL_SET_ERR_MSG_FMT_MOD(extack,
+ "Queue depth underflow[%d]: tx[%d], rx[%d]",
+ HINIC3_MIN_QUEUE_DEPTH,
+ ring->tx_pending, ring->rx_pending);
+
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int hinic3_set_ringparam(struct net_device *netdev,
+ struct ethtool_ringparam *ring,
+ struct kernel_ethtool_ringparam *kernel_ring,
+ struct netlink_ext_ack *extack)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ struct hinic3_dyna_txrxq_params q_params = {};
+ u32 new_sq_depth, new_rq_depth;
+ int err;
+
+ err = hinic3_check_ringparam_valid(netdev, ring, extack);
+ if (err)
+ return err;
+
+ new_sq_depth = rounddown_pow_of_two(ring->tx_pending);
+ new_rq_depth = rounddown_pow_of_two(ring->rx_pending);
+ if (new_sq_depth == nic_dev->q_params.sq_depth &&
+ new_rq_depth == nic_dev->q_params.rq_depth)
+ return 0;
+
+ if (new_sq_depth != ring->tx_pending ||
+ new_rq_depth != ring->rx_pending)
+ NL_SET_ERR_MSG_FMT_MOD(extack,
+ "Requested Tx/Rx ring depth %u/%u trimmed to %u/%u",
+ ring->tx_pending, ring->rx_pending,
+ new_sq_depth, new_rq_depth);
+
+ if (!netif_running(netdev)) {
+ hinic3_update_qp_depth(netdev, new_sq_depth, new_rq_depth);
+ } else {
+ q_params = nic_dev->q_params;
+ q_params.sq_depth = new_sq_depth;
+ q_params.rq_depth = new_rq_depth;
+
+ err = hinic3_change_channel_settings(netdev, &q_params);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
static const struct ethtool_ops hinic3_ethtool_ops = {
.supported_coalesce_params = ETHTOOL_COALESCE_USECS |
ETHTOOL_COALESCE_PKT_RATE_RX_USECS,
@@ -417,6 +506,8 @@ static const struct ethtool_ops hinic3_ethtool_ops = {
.get_msglevel = hinic3_get_msglevel,
.set_msglevel = hinic3_set_msglevel,
.get_link = ethtool_op_get_link,
+ .get_ringparam = hinic3_get_ringparam,
+ .set_ringparam = hinic3_set_ringparam,
};
void hinic3_set_ethtool_ops(struct net_device *netdev)
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_main.c b/drivers/net/ethernet/huawei/hinic3/hinic3_main.c
index 0a888fe4c975..1e7ac3f330d7 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_main.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_main.c
@@ -178,7 +178,7 @@ static int hinic3_sw_init(struct net_device *netdev)
u8 mac_addr[ETH_ALEN];
int err;
- mutex_init(&nic_dev->port_state_mutex);
+ mutex_init(&nic_dev->state_lock);
nic_dev->q_params.sq_depth = HINIC3_SQ_DEPTH;
nic_dev->q_params.rq_depth = HINIC3_RQ_DEPTH;
@@ -330,6 +330,8 @@ static void hinic3_link_status_change(struct net_device *netdev,
netif_carrier_off(netdev);
netdev_dbg(netdev, "Link is down\n");
}
+
+ return;
}
static void hinic3_port_module_event_handler(struct net_device *netdev,
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_netdev_ops.c b/drivers/net/ethernet/huawei/hinic3/hinic3_netdev_ops.c
index da73811641a9..7f304d7ac47b 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_netdev_ops.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_netdev_ops.c
@@ -288,7 +288,8 @@ static void hinic3_free_channel_resources(struct net_device *netdev,
hinic3_free_qps(nic_dev, qp_params);
}
-static int hinic3_open_channel(struct net_device *netdev)
+static int hinic3_prepare_channel(struct net_device *netdev,
+ struct hinic3_dyna_txrxq_params *qp_params)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
int err;
@@ -299,12 +300,25 @@ static int hinic3_open_channel(struct net_device *netdev)
return err;
}
- err = hinic3_configure_txrxqs(netdev, &nic_dev->q_params);
+ err = hinic3_configure_txrxqs(netdev, qp_params);
if (err) {
netdev_err(netdev, "Failed to configure txrxqs\n");
goto err_free_qp_ctxts;
}
+ return 0;
+
+err_free_qp_ctxts:
+ hinic3_free_qp_ctxts(nic_dev);
+
+ return err;
+}
+
+static int hinic3_open_channel(struct net_device *netdev)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ int err;
+
err = hinic3_qps_irq_init(netdev);
if (err) {
netdev_err(netdev, "Failed to init txrxq irq\n");
@@ -321,6 +335,7 @@ static int hinic3_open_channel(struct net_device *netdev)
err_uninit_qps_irq:
hinic3_qps_irq_uninit(netdev);
+
err_free_qp_ctxts:
hinic3_free_qp_ctxts(nic_dev);
@@ -341,9 +356,7 @@ static int hinic3_maybe_set_port_state(struct net_device *netdev, bool enable)
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
int err;
- mutex_lock(&nic_dev->port_state_mutex);
err = hinic3_set_port_enable(nic_dev->hwdev, enable);
- mutex_unlock(&nic_dev->port_state_mutex);
return err;
}
@@ -428,6 +441,76 @@ static void hinic3_vport_down(struct net_device *netdev)
}
}
+int
+hinic3_change_channel_settings(struct net_device *netdev,
+ struct hinic3_dyna_txrxq_params *trxq_params)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ struct hinic3_dyna_txrxq_params cur_trxq_params = {};
+ struct hinic3_dyna_qp_params new_qp_params = {};
+ struct hinic3_dyna_qp_params cur_qp_params = {};
+ int err;
+
+ mutex_lock(&nic_dev->state_lock);
+ cur_trxq_params = nic_dev->q_params;
+
+ hinic3_config_num_qps(netdev, trxq_params);
+
+ err = hinic3_alloc_channel_resources(netdev, &new_qp_params,
+ trxq_params);
+ if (err) {
+ netdev_err(netdev, "Failed to alloc channel resources\n");
+ hinic3_config_num_qps(netdev, &cur_trxq_params);
+ mutex_unlock(&nic_dev->state_lock);
+ return err;
+ }
+
+ hinic3_vport_down(netdev);
+ hinic3_close_channel(netdev);
+ hinic3_get_cur_qps(nic_dev, &cur_qp_params);
+
+ hinic3_init_qps(nic_dev, &new_qp_params);
+
+ err = hinic3_prepare_channel(netdev, trxq_params);
+ if (err)
+ goto err_uninit_qps;
+
+ if (nic_dev->num_qp_irq > trxq_params->num_qps)
+ hinic3_qp_irq_change(netdev, trxq_params->num_qps);
+
+ nic_dev->q_params = *trxq_params;
+
+ err = hinic3_open_channel(netdev);
+ if (err)
+ goto err_qp_irq_reset;
+
+ err = hinic3_vport_up(netdev);
+ if (err)
+ goto err_close_channel;
+
+ hinic3_free_channel_resources(netdev, &cur_qp_params, &cur_trxq_params);
+
+ mutex_unlock(&nic_dev->state_lock);
+
+ return 0;
+
+err_close_channel:
+ hinic3_close_channel(netdev);
+err_qp_irq_reset:
+ nic_dev->q_params = cur_trxq_params;
+
+ if (trxq_params->num_qps > cur_trxq_params.num_qps)
+ hinic3_qp_irq_change(netdev, cur_trxq_params.num_qps);
+err_uninit_qps:
+ hinic3_get_cur_qps(nic_dev, &new_qp_params);
+ hinic3_config_num_qps(netdev, &cur_trxq_params);
+ hinic3_init_qps(nic_dev, &cur_qp_params);
+ hinic3_free_channel_resources(netdev, &new_qp_params, trxq_params);
+ mutex_unlock(&nic_dev->state_lock);
+
+ return err;
+}
+
static int hinic3_open(struct net_device *netdev)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
@@ -458,6 +541,10 @@ static int hinic3_open(struct net_device *netdev)
hinic3_init_qps(nic_dev, &qp_params);
+ err = hinic3_prepare_channel(netdev, &nic_dev->q_params);
+ if (err)
+ goto err_uninit_qps;
+
err = hinic3_open_channel(netdev);
if (err)
goto err_uninit_qps;
@@ -473,7 +560,7 @@ static int hinic3_open(struct net_device *netdev)
err_close_channel:
hinic3_close_channel(netdev);
err_uninit_qps:
- hinic3_uninit_qps(nic_dev, &qp_params);
+ hinic3_get_cur_qps(nic_dev, &qp_params);
hinic3_free_channel_resources(netdev, &qp_params, &nic_dev->q_params);
err_destroy_num_qps:
hinic3_destroy_num_qps(netdev);
@@ -493,10 +580,15 @@ static int hinic3_close(struct net_device *netdev)
return 0;
}
+ mutex_lock(&nic_dev->state_lock);
hinic3_vport_down(netdev);
hinic3_close_channel(netdev);
- hinic3_uninit_qps(nic_dev, &qp_params);
- hinic3_free_channel_resources(netdev, &qp_params, &nic_dev->q_params);
+ hinic3_get_cur_qps(nic_dev, &qp_params);
+ hinic3_free_channel_resources(netdev, &qp_params,
+ &nic_dev->q_params);
+ hinic3_free_nicio_res(nic_dev);
+ hinic3_destroy_num_qps(netdev);
+ mutex_unlock(&nic_dev->state_lock);
return 0;
}
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_nic_dev.h b/drivers/net/ethernet/huawei/hinic3/hinic3_nic_dev.h
index 9502293ff710..ccef82d57fdd 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_nic_dev.h
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_nic_dev.h
@@ -10,6 +10,9 @@
#include "hinic3_hw_cfg.h"
#include "hinic3_hwdev.h"
#include "hinic3_mgmt_interface.h"
+#include "hinic3_nic_io.h"
+#include "hinic3_tx.h"
+#include "hinic3_rx.h"
#define HINIC3_VLAN_BITMAP_BYTE_SIZE(nic_dev) (sizeof(*(nic_dev)->vlan_bitmap))
#define HINIC3_VLAN_BITMAP_SIZE(nic_dev) \
@@ -127,8 +130,8 @@ struct hinic3_nic_dev {
struct workqueue_struct *workq;
struct delayed_work periodic_work;
struct work_struct rx_mode_work;
- /* lock for enable/disable port */
- struct mutex port_state_mutex;
+ /* Protects Interface state */
+ struct mutex state_lock;
struct list_head uc_filter_list;
struct list_head mc_filter_list;
@@ -143,6 +146,10 @@ struct hinic3_nic_dev {
void hinic3_set_netdev_ops(struct net_device *netdev);
int hinic3_set_hw_features(struct net_device *netdev);
+int
+hinic3_change_channel_settings(struct net_device *netdev,
+ struct hinic3_dyna_txrxq_params *trxq_params);
+
int hinic3_qps_irq_init(struct net_device *netdev);
void hinic3_qps_irq_uninit(struct net_device *netdev);
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_nic_io.c b/drivers/net/ethernet/huawei/hinic3/hinic3_nic_io.c
index 87e736adba02..0e7a0ccfba98 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_nic_io.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_nic_io.c
@@ -484,8 +484,8 @@ void hinic3_init_qps(struct hinic3_nic_dev *nic_dev,
}
}
-void hinic3_uninit_qps(struct hinic3_nic_dev *nic_dev,
- struct hinic3_dyna_qp_params *qp_params)
+void hinic3_get_cur_qps(struct hinic3_nic_dev *nic_dev,
+ struct hinic3_dyna_qp_params *qp_params)
{
struct hinic3_nic_io *nic_io = nic_dev->nic_io;
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_nic_io.h b/drivers/net/ethernet/huawei/hinic3/hinic3_nic_io.h
index 12eefabcf1db..571b34d63950 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_nic_io.h
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_nic_io.h
@@ -14,6 +14,10 @@ struct hinic3_nic_dev;
#define HINIC3_RQ_WQEBB_SHIFT 3
#define HINIC3_SQ_WQEBB_SIZE BIT(HINIC3_SQ_WQEBB_SHIFT)
+#define HINIC3_MAX_TX_QUEUE_DEPTH 65536
+#define HINIC3_MAX_RX_QUEUE_DEPTH 16384
+#define HINIC3_MIN_QUEUE_DEPTH 128
+
/* ******************** RQ_CTRL ******************** */
enum hinic3_rq_wqe_type {
HINIC3_NORMAL_RQ_WQE = 1,
@@ -136,8 +140,8 @@ void hinic3_free_qps(struct hinic3_nic_dev *nic_dev,
struct hinic3_dyna_qp_params *qp_params);
void hinic3_init_qps(struct hinic3_nic_dev *nic_dev,
struct hinic3_dyna_qp_params *qp_params);
-void hinic3_uninit_qps(struct hinic3_nic_dev *nic_dev,
- struct hinic3_dyna_qp_params *qp_params);
+void hinic3_get_cur_qps(struct hinic3_nic_dev *nic_dev,
+ struct hinic3_dyna_qp_params *qp_params);
int hinic3_init_qp_ctxts(struct hinic3_nic_dev *nic_dev);
void hinic3_free_qp_ctxts(struct hinic3_nic_dev *nic_dev);
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_rx.c b/drivers/net/ethernet/huawei/hinic3/hinic3_rx.c
index 309ab5901379..389b1c2158be 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_rx.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_rx.c
@@ -541,7 +541,7 @@ int hinic3_configure_rxqs(struct net_device *netdev, u16 num_rq,
rq_associate_cqes(rxq);
pkts = hinic3_rx_fill_buffers(rxq);
- if (!pkts) {
+ if (pkts < rxq->q_mask) {
netdev_err(netdev, "Failed to fill Rx buffer\n");
return -ENOMEM;
}
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_tx.c b/drivers/net/ethernet/huawei/hinic3/hinic3_tx.c
index 9306bf0020ca..53c065194136 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_tx.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_tx.c
@@ -261,8 +261,7 @@ static int hinic3_tx_csum(struct hinic3_txq *txq, struct hinic3_sq_task *task,
((struct udphdr *)skb_transport_header(skb))->dest !=
VXLAN_OFFLOAD_PORT_LE) {
/* Unsupported tunnel packet, disable csum offload */
- skb_checksum_help(skb);
- return 0;
+ return skb_checksum_help(skb);
}
}
@@ -412,6 +411,10 @@ static u32 hinic3_tx_offload(struct sk_buff *skb, struct hinic3_sq_task *task,
offload |= HINIC3_TX_OFFLOAD_TSO;
} else {
tso_cs_en = hinic3_tx_csum(txq, task, skb);
+ if (tso_cs_en < 0) {
+ offload = HINIC3_TX_OFFLOAD_INVALID;
+ return offload;
+ }
if (tso_cs_en)
offload |= HINIC3_TX_OFFLOAD_CSUM;
}
--
2.54.0
^ permalink raw reply related
* [PATCH net-next v11 4/5] hinic3: Add ethtool rss ops
From: Fan Gong @ 2026-07-20 12:53 UTC (permalink / raw)
To: Fan Gong, Wu Di, Teng Peisen, netdev, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Andrew Lunn, Ioana Ciornei, Mohsin Bashir, Dimitri Daskalakis,
Harshitha Ramamurthy
Cc: linux-kernel, linux-doc, luosifu, Xin Guo, Zhou Shuai, Wu Like,
Shi Jing, Zheng Jiezhen, Maxime Chevallier
In-Reply-To: <cover.1784538912.git.root@localhost.localdomain>
Implement following ethtool callback function:
.get_rxnfc
.set_rxnfc
.get_channels
.set_channels
.get_rxfh_indir_size
.get_rxfh_key_size
.get_rxfh
.set_rxfh
These callbacks allow users to utilize ethtool for detailed
RSS parameters configuration and monitoring.
Co-developed-by: Wu Di <wudi234@huawei.com>
Signed-off-by: Wu Di <wudi234@huawei.com>
Co-developed-by: Teng Peisen <tengpeisen@huawei.com>
Signed-off-by: Teng Peisen <tengpeisen@huawei.com>
Signed-off-by: Fan Gong <gongfan1@huawei.com>
---
.../ethernet/huawei/hinic3/hinic3_ethtool.c | 10 +
.../huawei/hinic3/hinic3_mgmt_interface.h | 2 +
.../net/ethernet/huawei/hinic3/hinic3_rss.c | 561 +++++++++++++++++-
.../net/ethernet/huawei/hinic3/hinic3_rss.h | 20 +
4 files changed, 591 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c b/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c
index ee5abb875ad2..c65f6a453626 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c
@@ -16,6 +16,7 @@
#include "hinic3_hw_comm.h"
#include "hinic3_nic_dev.h"
#include "hinic3_nic_cfg.h"
+#include "hinic3_rss.h"
#define HINIC3_MGMT_VERSION_MAX_LEN 32
/* Coalesce time properties in microseconds */
@@ -1273,6 +1274,15 @@ static const struct ethtool_ops hinic3_ethtool_ops = {
.get_pause_stats = hinic3_get_pause_stats,
.get_coalesce = hinic3_get_coalesce,
.set_coalesce = hinic3_set_coalesce,
+ .get_rxnfc = hinic3_get_rxnfc,
+ .set_rxnfc = hinic3_set_rxnfc,
+ .get_rx_ring_count = hinic3_get_rx_ring_count,
+ .get_channels = hinic3_get_channels,
+ .set_channels = hinic3_set_channels,
+ .get_rxfh_indir_size = hinic3_get_rxfh_indir_size,
+ .get_rxfh_key_size = hinic3_get_rxfh_key_size,
+ .get_rxfh = hinic3_get_rxfh,
+ .set_rxfh = hinic3_set_rxfh,
};
void hinic3_set_ethtool_ops(struct net_device *netdev)
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_mgmt_interface.h b/drivers/net/ethernet/huawei/hinic3/hinic3_mgmt_interface.h
index 76c691f82703..3c1263ff99ff 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_mgmt_interface.h
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_mgmt_interface.h
@@ -282,6 +282,7 @@ enum l2nic_cmd {
L2NIC_CMD_SET_VLAN_FILTER_EN = 26,
L2NIC_CMD_SET_RX_VLAN_OFFLOAD = 27,
L2NIC_CMD_CFG_RSS = 60,
+ L2NIC_CMD_GET_RSS_CTX_TBL = 62,
L2NIC_CMD_CFG_RSS_HASH_KEY = 63,
L2NIC_CMD_CFG_RSS_HASH_ENGINE = 64,
L2NIC_CMD_SET_RSS_CTX_TBL = 65,
@@ -301,6 +302,7 @@ enum l2nic_ucode_cmd {
L2NIC_UCODE_CMD_MODIFY_QUEUE_CTX = 0,
L2NIC_UCODE_CMD_CLEAN_QUEUE_CTX = 1,
L2NIC_UCODE_CMD_SET_RSS_INDIR_TBL = 4,
+ L2NIC_UCODE_CMD_GET_RSS_INDIR_TBL = 6,
};
/* hilink mac group command */
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_rss.c b/drivers/net/ethernet/huawei/hinic3/hinic3_rss.c
index 25db74d8c7dd..62cf5f59a0cf 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_rss.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_rss.c
@@ -155,7 +155,7 @@ static int hinic3_set_rss_type(struct hinic3_hwdev *hwdev,
L2NIC_CMD_SET_RSS_CTX_TBL, &msg_params);
if (ctx_tbl.msg_head.status == MGMT_STATUS_CMD_UNSUPPORTED) {
- return MGMT_STATUS_CMD_UNSUPPORTED;
+ return -EOPNOTSUPP;
} else if (err || ctx_tbl.msg_head.status) {
dev_err(hwdev->dev, "mgmt Failed to set rss context offload, err: %d, status: 0x%x\n",
err, ctx_tbl.msg_head.status);
@@ -165,6 +165,41 @@ static int hinic3_set_rss_type(struct hinic3_hwdev *hwdev,
return 0;
}
+static int hinic3_get_rss_type(struct hinic3_hwdev *hwdev,
+ struct hinic3_rss_type *rss_type)
+{
+ struct l2nic_cmd_rss_ctx_tbl ctx_tbl = {};
+ struct mgmt_msg_params msg_params = {};
+ int err;
+
+ ctx_tbl.func_id = hinic3_global_func_id(hwdev);
+
+ mgmt_msg_params_init_default(&msg_params, &ctx_tbl, sizeof(ctx_tbl));
+
+ err = hinic3_send_mbox_to_mgmt(hwdev, MGMT_MOD_L2NIC,
+ L2NIC_CMD_GET_RSS_CTX_TBL,
+ &msg_params);
+ if (ctx_tbl.msg_head.status == MGMT_STATUS_CMD_UNSUPPORTED) {
+ return -EOPNOTSUPP;
+ } else if (err || ctx_tbl.msg_head.status) {
+ dev_err(hwdev->dev, "Failed to get hash type, err: %d, status: 0x%x\n",
+ err, ctx_tbl.msg_head.status);
+ return -EINVAL;
+ }
+
+ rss_type->ipv4 = L2NIC_RSS_TYPE_GET(ctx_tbl.context, IPV4);
+ rss_type->ipv6 = L2NIC_RSS_TYPE_GET(ctx_tbl.context, IPV6);
+ rss_type->ipv6_ext = L2NIC_RSS_TYPE_GET(ctx_tbl.context, IPV6_EXT);
+ rss_type->tcp_ipv4 = L2NIC_RSS_TYPE_GET(ctx_tbl.context, TCP_IPV4);
+ rss_type->tcp_ipv6 = L2NIC_RSS_TYPE_GET(ctx_tbl.context, TCP_IPV6);
+ rss_type->tcp_ipv6_ext = L2NIC_RSS_TYPE_GET(ctx_tbl.context,
+ TCP_IPV6_EXT);
+ rss_type->udp_ipv4 = L2NIC_RSS_TYPE_GET(ctx_tbl.context, UDP_IPV4);
+ rss_type->udp_ipv6 = L2NIC_RSS_TYPE_GET(ctx_tbl.context, UDP_IPV6);
+
+ return 0;
+}
+
static int hinic3_rss_cfg_hash_type(struct hinic3_hwdev *hwdev, u8 opcode,
enum hinic3_rss_hash_type *type)
{
@@ -264,7 +299,8 @@ static int hinic3_set_hw_rss_parameters(struct net_device *netdev, u8 rss_en)
if (err)
return err;
- hinic3_fillout_indir_tbl(netdev, nic_dev->rss_indir);
+ if (!netif_is_rxfh_configured(netdev))
+ hinic3_fillout_indir_tbl(netdev, nic_dev->rss_indir);
err = hinic3_config_rss_hw_resource(netdev, nic_dev->rss_indir);
if (err)
@@ -334,3 +370,524 @@ void hinic3_try_to_enable_rss(struct net_device *netdev)
clear_bit(HINIC3_RSS_ENABLE, &nic_dev->flags);
nic_dev->q_params.num_qps = nic_dev->max_qps;
}
+
+static int hinic3_set_l4_rss_hash_ops(const struct ethtool_rxnfc *cmd,
+ struct hinic3_rss_type *rss_type)
+{
+ u8 rss_l4_en;
+
+ switch (cmd->data & (RXH_L4_B_0_1 | RXH_L4_B_2_3)) {
+ case 0:
+ rss_l4_en = 0;
+ break;
+ case (RXH_L4_B_0_1 | RXH_L4_B_2_3):
+ rss_l4_en = 1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (cmd->flow_type) {
+ case TCP_V4_FLOW:
+ rss_type->tcp_ipv4 = rss_l4_en;
+ break;
+ case TCP_V6_FLOW:
+ rss_type->tcp_ipv6 = rss_l4_en;
+ break;
+ case UDP_V4_FLOW:
+ rss_type->udp_ipv4 = rss_l4_en;
+ break;
+ case UDP_V6_FLOW:
+ rss_type->udp_ipv6 = rss_l4_en;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int hinic3_update_rss_hash_opts(struct net_device *netdev,
+ struct ethtool_rxnfc *cmd,
+ struct hinic3_rss_type *rss_type)
+{
+ int err;
+
+ switch (cmd->flow_type) {
+ case TCP_V4_FLOW:
+ case TCP_V6_FLOW:
+ case UDP_V4_FLOW:
+ case UDP_V6_FLOW:
+ err = hinic3_set_l4_rss_hash_ops(cmd, rss_type);
+ if (err)
+ return err;
+
+ break;
+ case IPV4_FLOW:
+ rss_type->ipv4 = (cmd->data != 0) ? 1 : 0;
+ break;
+ case IPV6_FLOW:
+ rss_type->ipv6 = (cmd->data != 0) ? 1 : 0;
+ break;
+ default:
+ netdev_err(netdev, "Unsupported flow type\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int hinic3_set_rss_hash_opts(struct net_device *netdev,
+ struct ethtool_rxnfc *cmd)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ struct hinic3_rss_type rss_type;
+ int err;
+
+ if (!test_bit(HINIC3_RSS_ENABLE, &nic_dev->flags)) {
+ cmd->data = 0;
+ netdev_err(netdev, "RSS is disable, not support to set flow-hash\n");
+ return -EOPNOTSUPP;
+ }
+
+ /* RSS only supports hashing of IP addresses and L4 ports */
+ if (cmd->data & ~(RXH_IP_SRC | RXH_IP_DST |
+ RXH_L4_B_0_1 | RXH_L4_B_2_3))
+ return -EINVAL;
+
+ /* Both IP addresses must be part of the hash tuple */
+ if (cmd->data &&
+ (!(cmd->data & RXH_IP_SRC) || !(cmd->data & RXH_IP_DST)))
+ return -EINVAL;
+
+ /* L4 hash bits are not valid for pure L3 flow types */
+ if ((cmd->flow_type == IPV4_FLOW || cmd->flow_type == IPV6_FLOW) &&
+ (cmd->data & (RXH_L4_B_0_1 | RXH_L4_B_2_3)))
+ return -EINVAL;
+
+ err = hinic3_get_rss_type(nic_dev->hwdev, &rss_type);
+ if (err) {
+ netdev_err(netdev, "Failed to get rss type\n");
+ return err;
+ }
+
+ err = hinic3_update_rss_hash_opts(netdev, cmd, &rss_type);
+ if (err)
+ return err;
+
+ err = hinic3_set_rss_type(nic_dev->hwdev, rss_type);
+ if (err) {
+ netdev_err(netdev, "Failed to set rss type\n");
+ return err;
+ }
+
+ nic_dev->rss_type = rss_type;
+
+ return 0;
+}
+
+static void convert_rss_l3_type(u8 rss_opt, struct ethtool_rxnfc *cmd)
+{
+ if (!rss_opt)
+ cmd->data &= ~(RXH_IP_SRC | RXH_IP_DST);
+}
+
+static void convert_rss_l4_type(u8 rss_opt, struct ethtool_rxnfc *cmd)
+{
+ if (rss_opt)
+ cmd->data |= RXH_L4_B_0_1 | RXH_L4_B_2_3;
+}
+
+static int hinic3_convert_rss_type(struct net_device *netdev,
+ struct hinic3_rss_type *rss_type,
+ struct ethtool_rxnfc *cmd)
+{
+ cmd->data = RXH_IP_SRC | RXH_IP_DST;
+ switch (cmd->flow_type) {
+ case TCP_V4_FLOW:
+ convert_rss_l4_type(rss_type->tcp_ipv4, cmd);
+ break;
+ case TCP_V6_FLOW:
+ convert_rss_l4_type(rss_type->tcp_ipv6, cmd);
+ break;
+ case UDP_V4_FLOW:
+ convert_rss_l4_type(rss_type->udp_ipv4, cmd);
+ break;
+ case UDP_V6_FLOW:
+ convert_rss_l4_type(rss_type->udp_ipv6, cmd);
+ break;
+ case IPV4_FLOW:
+ convert_rss_l3_type(rss_type->ipv4, cmd);
+ break;
+ case IPV6_FLOW:
+ convert_rss_l3_type(rss_type->ipv6, cmd);
+ break;
+ default:
+ netdev_err(netdev, "Unsupported flow type\n");
+ cmd->data = 0;
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int hinic3_get_rss_hash_opts(struct net_device *netdev,
+ struct ethtool_rxnfc *cmd)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ struct hinic3_rss_type rss_type;
+ int err;
+
+ cmd->data = 0;
+
+ if (!test_bit(HINIC3_RSS_ENABLE, &nic_dev->flags))
+ return 0;
+
+ err = hinic3_get_rss_type(nic_dev->hwdev, &rss_type);
+ if (err) {
+ netdev_err(netdev, "Failed to get rss type\n");
+ return err;
+ }
+
+ return hinic3_convert_rss_type(netdev, &rss_type, cmd);
+}
+
+u32 hinic3_get_rx_ring_count(struct net_device *netdev)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+
+ return nic_dev->q_params.num_qps;
+}
+
+int hinic3_get_rxnfc(struct net_device *netdev,
+ struct ethtool_rxnfc *cmd, u32 *rule_locs)
+{
+ int err;
+
+ switch (cmd->cmd) {
+ case ETHTOOL_GRXFH:
+ err = hinic3_get_rss_hash_opts(netdev, cmd);
+ break;
+ default:
+ err = -EOPNOTSUPP;
+ break;
+ }
+
+ return err;
+}
+
+int hinic3_set_rxnfc(struct net_device *netdev, struct ethtool_rxnfc *cmd)
+{
+ int err;
+
+ switch (cmd->cmd) {
+ case ETHTOOL_SRXFH:
+ err = hinic3_set_rss_hash_opts(netdev, cmd);
+ break;
+ default:
+ err = -EOPNOTSUPP;
+ break;
+ }
+
+ return err;
+}
+
+static u16 hinic3_max_channels(struct net_device *netdev)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ u8 tcs = netdev_get_num_tc(netdev);
+
+ return tcs ? nic_dev->max_qps / tcs : nic_dev->max_qps;
+}
+
+static u16 hinic3_curr_channels(struct net_device *netdev)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+
+ if (netif_running(netdev))
+ return nic_dev->q_params.num_qps ?
+ nic_dev->q_params.num_qps : 1;
+ else
+ return min_t(u16, hinic3_max_channels(netdev),
+ nic_dev->q_params.num_qps);
+}
+
+void hinic3_get_channels(struct net_device *netdev,
+ struct ethtool_channels *channels)
+{
+ channels->max_rx = 0;
+ channels->max_tx = 0;
+ channels->max_other = 0;
+ /* report maximum channels */
+ channels->max_combined = hinic3_max_channels(netdev);
+ channels->rx_count = 0;
+ channels->tx_count = 0;
+ channels->other_count = 0;
+ /* report flow director queues as maximum channels */
+ channels->combined_count = hinic3_curr_channels(netdev);
+}
+
+static int
+hinic3_validate_channel_parameter(struct net_device *netdev,
+ const struct ethtool_channels *channels)
+{
+ u16 max_channel = hinic3_max_channels(netdev);
+ unsigned int count = channels->combined_count;
+
+ if (!count) {
+ netdev_err(netdev, "Unsupported combined_count=0\n");
+ return -EINVAL;
+ }
+
+ if (channels->tx_count || channels->rx_count || channels->other_count) {
+ netdev_err(netdev, "Setting rx/tx/other count not supported\n");
+ return -EINVAL;
+ }
+
+ if (count > max_channel) {
+ netdev_err(netdev, "Combined count %u exceed limit %u\n", count,
+ max_channel);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int hinic3_rss_update_num_qps_and_reprogram(struct net_device *netdev)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ int err;
+
+ if (!netif_is_rxfh_configured(netdev)) {
+ hinic3_fillout_indir_tbl(netdev, nic_dev->rss_indir);
+ hinic3_rss_set_indir_tbl(nic_dev->hwdev, nic_dev->rss_indir);
+ }
+
+ if (!netif_running(netdev))
+ return 0;
+
+ err = hinic3_set_hw_rss_parameters(netdev, 1);
+ if (err)
+ netdev_err(netdev,
+ "Failed to update RSS parameters after changing channels\n");
+
+ return err;
+}
+
+int hinic3_set_channels(struct net_device *netdev,
+ struct ethtool_channels *channels)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ unsigned int count = channels->combined_count;
+ struct hinic3_dyna_txrxq_params q_params;
+ u16 old_num_qps;
+ int err;
+
+ err = hinic3_validate_channel_parameter(netdev, channels);
+ if (err)
+ return err;
+
+ if (!test_bit(HINIC3_RSS_ENABLE, &nic_dev->flags)) {
+ netdev_err(netdev, "This function doesn't support RSS, only support 1 queue pair\n");
+ return -EOPNOTSUPP;
+ }
+
+ old_num_qps = nic_dev->q_params.num_qps;
+
+ netdev_dbg(netdev, "Set max combined queue number from %u to %u\n",
+ old_num_qps, count);
+
+ if (netif_running(netdev)) {
+ q_params = nic_dev->q_params;
+ q_params.num_qps = (u16)count;
+ q_params.txqs_res = NULL;
+ q_params.rxqs_res = NULL;
+ q_params.irq_cfg = NULL;
+
+ err = hinic3_change_channel_settings(netdev, &q_params);
+ if (err) {
+ netdev_err(netdev, "Failed to change channel settings\n");
+ return err;
+ }
+ } else {
+ nic_dev->q_params.num_qps = (u16)count;
+ }
+
+ err = hinic3_rss_update_num_qps_and_reprogram(netdev);
+ if (err) {
+ if (netif_running(netdev)) {
+ q_params.num_qps = old_num_qps;
+ if (hinic3_change_channel_settings(netdev, &q_params))
+ netdev_err(netdev, "Failed to rollback channel settings after RSS error\n");
+ } else {
+ nic_dev->q_params.num_qps = old_num_qps;
+ }
+ return err;
+ }
+
+ return 0;
+}
+
+u32 hinic3_get_rxfh_indir_size(struct net_device *netdev)
+{
+ return L2NIC_RSS_INDIR_SIZE;
+}
+
+static int hinic3_set_rss_rxfh(struct net_device *netdev,
+ const u32 *indir, u8 *key)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ u16 temp_indir[L2NIC_RSS_INDIR_SIZE];
+ int err;
+ u32 i;
+
+ if (indir) {
+ for (i = 0; i < L2NIC_RSS_INDIR_SIZE; i++)
+ temp_indir[i] = (u16)indir[i];
+
+ err = hinic3_rss_set_indir_tbl(nic_dev->hwdev, temp_indir);
+ if (err) {
+ netdev_err(netdev, "Failed to set rss indir table\n");
+ return err;
+ }
+
+ memcpy(nic_dev->rss_indir, temp_indir, sizeof(temp_indir));
+ }
+
+ if (key) {
+ err = hinic3_rss_set_hash_key(nic_dev->hwdev, key);
+ if (err) {
+ netdev_err(netdev, "Failed to set rss key\n");
+ return err;
+ }
+
+ memcpy(nic_dev->rss_hkey, key, L2NIC_RSS_KEY_SIZE);
+ }
+
+ return 0;
+}
+
+u32 hinic3_get_rxfh_key_size(struct net_device *netdev)
+{
+ return L2NIC_RSS_KEY_SIZE;
+}
+
+static int hinic3_rss_get_indir_tbl(struct hinic3_hwdev *hwdev,
+ u32 *indir_table)
+{
+ struct hinic3_cmd_buf_pair pair;
+ __le16 *indir_tbl = NULL;
+ int err, i;
+
+ err = hinic3_cmd_buf_pair_init(hwdev, &pair);
+ if (err) {
+ dev_err(hwdev->dev, "Failed to allocate cmd_buf.\n");
+ return err;
+ }
+
+ memset(pair.in->buf, 0, le16_to_cpu(pair.in->size));
+
+ err = hinic3_cmdq_detail_resp(hwdev, MGMT_MOD_L2NIC,
+ L2NIC_UCODE_CMD_GET_RSS_INDIR_TBL,
+ pair.in, pair.out, NULL);
+ if (err) {
+ dev_err(hwdev->dev, "Failed to get rss indir table\n");
+ goto err_get_indir_tbl;
+ }
+
+ indir_tbl = (__force __le16 *)pair.out->buf;
+ for (i = 0; i < L2NIC_RSS_INDIR_SIZE; i++)
+ indir_table[i] = le16_to_cpu(*(indir_tbl + i));
+
+err_get_indir_tbl:
+ hinic3_cmd_buf_pair_uninit(hwdev, &pair);
+
+ return err;
+}
+
+int hinic3_get_rxfh(struct net_device *netdev,
+ struct ethtool_rxfh_param *rxfh)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ int err = 0;
+
+ if (!test_bit(HINIC3_RSS_ENABLE, &nic_dev->flags)) {
+ netdev_err(netdev, "Rss is disabled\n");
+ return -EOPNOTSUPP;
+ }
+
+ rxfh->hfunc =
+ nic_dev->rss_hash_type == HINIC3_RSS_HASH_ENGINE_TYPE_XOR ?
+ ETH_RSS_HASH_XOR : ETH_RSS_HASH_TOP;
+
+ if (rxfh->indir) {
+ err = hinic3_rss_get_indir_tbl(nic_dev->hwdev, rxfh->indir);
+ if (err)
+ return err;
+ }
+
+ if (rxfh->key)
+ memcpy(rxfh->key, nic_dev->rss_hkey, L2NIC_RSS_KEY_SIZE);
+
+ return err;
+}
+
+static int hinic3_update_hash_func_type(struct net_device *netdev, u8 hfunc)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ enum hinic3_rss_hash_type new_rss_hash_type;
+ int err;
+
+ switch (hfunc) {
+ case ETH_RSS_HASH_NO_CHANGE:
+ return 0;
+ case ETH_RSS_HASH_XOR:
+ new_rss_hash_type = HINIC3_RSS_HASH_ENGINE_TYPE_XOR;
+ break;
+ case ETH_RSS_HASH_TOP:
+ new_rss_hash_type = HINIC3_RSS_HASH_ENGINE_TYPE_TOEP;
+ break;
+ default:
+ netdev_err(netdev, "Unsupported hash func %u\n", hfunc);
+ return -EOPNOTSUPP;
+ }
+
+ if (new_rss_hash_type == nic_dev->rss_hash_type)
+ return 0;
+
+ err = hinic3_rss_set_hash_type(nic_dev->hwdev, new_rss_hash_type);
+ if (err) {
+ netdev_err(netdev, "Failed to set RSS hash type to HW\n");
+ return err;
+ }
+
+ nic_dev->rss_hash_type = new_rss_hash_type;
+
+ return 0;
+}
+
+int hinic3_set_rxfh(struct net_device *netdev,
+ struct ethtool_rxfh_param *rxfh,
+ struct netlink_ext_ack *extack)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ u8 old_hfunc;
+ int err;
+
+ if (!test_bit(HINIC3_RSS_ENABLE, &nic_dev->flags)) {
+ netdev_err(netdev, "Not support to set rss parameters when rss is disable\n");
+ return -EOPNOTSUPP;
+ }
+
+ old_hfunc = nic_dev->rss_hash_type;
+
+ err = hinic3_update_hash_func_type(netdev, rxfh->hfunc);
+ if (err)
+ return err;
+
+ err = hinic3_set_rss_rxfh(netdev, rxfh->indir, rxfh->key);
+ if (err)
+ hinic3_update_hash_func_type(netdev, old_hfunc);
+
+ return err;
+}
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_rss.h b/drivers/net/ethernet/huawei/hinic3/hinic3_rss.h
index 78d82c2aca06..cf48a6449404 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_rss.h
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_rss.h
@@ -5,10 +5,30 @@
#define _HINIC3_RSS_H_
#include <linux/netdevice.h>
+#include <linux/ethtool.h>
int hinic3_rss_init(struct net_device *netdev);
void hinic3_rss_uninit(struct net_device *netdev);
void hinic3_try_to_enable_rss(struct net_device *netdev);
void hinic3_clear_rss_config(struct net_device *netdev);
+int hinic3_get_rxnfc(struct net_device *netdev,
+ struct ethtool_rxnfc *cmd, u32 *rule_locs);
+int hinic3_set_rxnfc(struct net_device *netdev, struct ethtool_rxnfc *cmd);
+
+void hinic3_get_channels(struct net_device *netdev,
+ struct ethtool_channels *channels);
+int hinic3_set_channels(struct net_device *netdev,
+ struct ethtool_channels *channels);
+
+u32 hinic3_get_rxfh_indir_size(struct net_device *netdev);
+u32 hinic3_get_rxfh_key_size(struct net_device *netdev);
+
+u32 hinic3_get_rx_ring_count(struct net_device *netdev);
+int hinic3_get_rxfh(struct net_device *netdev,
+ struct ethtool_rxfh_param *rxfh);
+int hinic3_set_rxfh(struct net_device *netdev,
+ struct ethtool_rxfh_param *rxfh,
+ struct netlink_ext_ack *extack);
+
#endif
--
2.54.0
^ permalink raw reply related
* [PATCH net-next v11 3/5] hinic3: Add ethtool coalesce ops
From: Fan Gong @ 2026-07-20 12:53 UTC (permalink / raw)
To: Fan Gong, Wu Di, Teng Peisen, netdev, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Andrew Lunn, Ioana Ciornei, Mohsin Bashir, Dimitri Daskalakis,
Harshitha Ramamurthy
Cc: linux-kernel, linux-doc, luosifu, Xin Guo, Zhou Shuai, Wu Like,
Shi Jing, Zheng Jiezhen, Maxime Chevallier
In-Reply-To: <cover.1784538912.git.root@localhost.localdomain>
Implement following ethtool callback function:
.get_coalesce
.set_coalesce
These callbacks allow users to utilize ethtool for detailed
RX coalesce configuration and monitoring.
Co-developed-by: Wu Di <wudi234@huawei.com>
Signed-off-by: Wu Di <wudi234@huawei.com>
Co-developed-by: Teng Peisen <tengpeisen@huawei.com>
Signed-off-by: Teng Peisen <tengpeisen@huawei.com>
Signed-off-by: Fan Gong <gongfan1@huawei.com>
---
.../ethernet/huawei/hinic3/hinic3_ethtool.c | 286 +++++++++++++++++-
.../net/ethernet/huawei/hinic3/hinic3_irq.c | 12 +-
.../net/ethernet/huawei/hinic3/hinic3_main.c | 2 +-
3 files changed, 293 insertions(+), 7 deletions(-)
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c b/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c
index 0e1f83806933..ee5abb875ad2 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c
@@ -18,6 +18,11 @@
#include "hinic3_nic_cfg.h"
#define HINIC3_MGMT_VERSION_MAX_LEN 32
+/* Coalesce time properties in microseconds */
+#define COALESCE_PENDING_LIMIT_UNIT 8
+#define COALESCE_TIMER_CFG_UNIT 5
+#define COALESCE_MAX_PENDING_LIMIT (255 * COALESCE_PENDING_LIMIT_UNIT)
+#define COALESCE_MAX_TIMER_CFG (255 * COALESCE_TIMER_CFG_UNIT)
static void hinic3_get_drvinfo(struct net_device *netdev,
struct ethtool_drvinfo *info)
@@ -973,9 +978,284 @@ static void hinic3_get_pause_stats(struct net_device *netdev,
kfree(ps);
}
+static int hinic3_set_queue_coalesce(struct net_device *netdev, u16 q_id,
+ struct hinic3_intr_coal_info *coal,
+ struct netlink_ext_ack *extack)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ struct hinic3_intr_coal_info *intr_coal;
+ struct hinic3_interrupt_info info = {};
+ bool is_active;
+ int err;
+
+ mutex_lock(&nic_dev->state_lock);
+ is_active = test_bit(HINIC3_INTF_UP, &nic_dev->flags) &&
+ (q_id < nic_dev->q_params.num_qps);
+
+ if (is_active) {
+ info.msix_index =
+ nic_dev->q_params.irq_cfg[q_id].msix_entry_idx;
+ info.resend_timer_cfg =
+ nic_dev->intr_coalesce[q_id].resend_timer_cfg;
+ }
+ mutex_unlock(&nic_dev->state_lock);
+
+ if (is_active) {
+ info.interrupt_coalesc_set = 1;
+ info.coalesc_timer_cfg = coal->coalesce_timer_cfg;
+ info.pending_limit = coal->pending_limit;
+
+ err = hinic3_set_interrupt_cfg(nic_dev->hwdev, info);
+ if (err) {
+ NL_SET_ERR_MSG_FMT_MOD(extack,
+ "Failed to set queue%u coalesce",
+ q_id);
+ return err;
+ }
+ }
+
+ mutex_lock(&nic_dev->state_lock);
+ intr_coal = &nic_dev->intr_coalesce[q_id];
+ intr_coal->coalesce_timer_cfg = coal->coalesce_timer_cfg;
+ intr_coal->pending_limit = coal->pending_limit;
+ intr_coal->rx_pending_limit_low = coal->rx_pending_limit_low;
+ intr_coal->rx_pending_limit_high = coal->rx_pending_limit_high;
+ mutex_unlock(&nic_dev->state_lock);
+
+ return 0;
+}
+
+static int is_coalesce_exceed_limit(const struct ethtool_coalesce *coal,
+ struct netlink_ext_ack *extack)
+{
+ const struct {
+ const char *name;
+ u32 value;
+ u32 limit;
+ } coalesce_limits[] = {
+ {"rx_coalesce_usecs",
+ coal->rx_coalesce_usecs,
+ COALESCE_MAX_TIMER_CFG},
+ {"rx_max_coalesced_frames",
+ coal->rx_max_coalesced_frames,
+ COALESCE_MAX_PENDING_LIMIT},
+ {"rx_max_coalesced_frames_low",
+ coal->rx_max_coalesced_frames_low,
+ COALESCE_MAX_PENDING_LIMIT},
+ {"rx_max_coalesced_frames_high",
+ coal->rx_max_coalesced_frames_high,
+ COALESCE_MAX_PENDING_LIMIT},
+ };
+
+ for (int i = 0; i < ARRAY_SIZE(coalesce_limits); i++) {
+ if (coalesce_limits[i].value > coalesce_limits[i].limit) {
+ NL_SET_ERR_MSG_FMT_MOD(extack, "%s out of range %d-%d",
+ coalesce_limits[i].name, 0,
+ coalesce_limits[i].limit);
+ return -ERANGE;
+ }
+ }
+ return 0;
+}
+
+static int hinic3_validate_coalesce(const struct ethtool_coalesce *coal,
+ struct netlink_ext_ack *extack)
+{
+ int err;
+
+ err = is_coalesce_exceed_limit(coal, extack);
+ if (err)
+ return err;
+
+ if (coal->rx_max_coalesced_frames_low >
+ coal->rx_max_coalesced_frames_high) {
+ NL_SET_ERR_MSG_FMT_MOD(extack,
+ "invalid coalesce frame high %u, low %u",
+ coal->rx_max_coalesced_frames_high,
+ coal->rx_max_coalesced_frames_low);
+ return -ERANGE;
+ }
+
+ return 0;
+}
+
+static void check_coalesce_align(struct net_device *netdev,
+ u32 item, u32 unit, const char *str)
+{
+ if (item % unit)
+ netdev_warn(netdev, "%s in %d units, change to %u\n",
+ str, unit, item - item % unit);
+}
+
+#define CHECK_COALESCE_ALIGN(member, unit) \
+ check_coalesce_align(netdev, member, unit, #member)
+
+static void check_coalesce_changed(struct net_device *netdev,
+ u32 item, u32 unit, u32 ori_val,
+ const char *obj_str, const char *str)
+{
+ if ((item / unit) != ori_val)
+ netdev_dbg(netdev, "Change %s from %d to %u %s\n",
+ str, ori_val * unit, item - item % unit, obj_str);
+}
+
+#define CHECK_COALESCE_CHANGED(member, unit, ori_val, obj_str) \
+ check_coalesce_changed(netdev, member, unit, ori_val, obj_str, #member)
+
+static int hinic3_set_hw_coal_param(struct net_device *netdev,
+ struct hinic3_intr_coal_info *intr_coal,
+ struct netlink_ext_ack *extack)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ int err;
+ u16 i;
+
+ for (i = 0; i < nic_dev->max_qps; i++) {
+ err = hinic3_set_queue_coalesce(netdev, i, intr_coal, extack);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+static int hinic3_get_coalesce(struct net_device *netdev,
+ struct ethtool_coalesce *coal,
+ struct kernel_ethtool_coalesce *kernel_coal,
+ struct netlink_ext_ack *extack)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ struct hinic3_intr_coal_info *interrupt_info;
+
+ interrupt_info = &nic_dev->intr_coalesce[0];
+
+ coal->use_adaptive_rx_coalesce = READ_ONCE(nic_dev->adaptive_rx_coal);
+
+ mutex_lock(&nic_dev->state_lock);
+
+ coal->rx_max_coalesced_frames_low =
+ interrupt_info->rx_pending_limit_low *
+ COALESCE_PENDING_LIMIT_UNIT;
+
+ coal->rx_max_coalesced_frames_high =
+ interrupt_info->rx_pending_limit_high *
+ COALESCE_PENDING_LIMIT_UNIT;
+
+ /* TX/RX uses the same interrupt.
+ * So we only declare RX ethtool_coalesce parameters.
+ */
+ coal->rx_coalesce_usecs = interrupt_info->coalesce_timer_cfg *
+ COALESCE_TIMER_CFG_UNIT;
+
+ coal->rx_max_coalesced_frames = interrupt_info->pending_limit *
+ COALESCE_PENDING_LIMIT_UNIT;
+
+ mutex_unlock(&nic_dev->state_lock);
+
+ return 0;
+}
+
+static int hinic3_set_coalesce(struct net_device *netdev,
+ struct ethtool_coalesce *coal,
+ struct kernel_ethtool_coalesce *kernel_coal,
+ struct netlink_ext_ack *extack)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ struct hinic3_intr_coal_info *ori_intr_coal;
+ struct hinic3_intr_coal_info intr_coal = {};
+ const char *obj_str = "for netdev";
+ bool old_adaptive, new_adaptive;
+ struct hinic3_rxq *rxq = NULL;
+ bool dim_stopped = false;
+ int err;
+ int i;
+
+ err = hinic3_validate_coalesce(coal, extack);
+ if (err)
+ return err;
+
+ CHECK_COALESCE_ALIGN(coal->rx_coalesce_usecs, COALESCE_TIMER_CFG_UNIT);
+ CHECK_COALESCE_ALIGN(coal->rx_max_coalesced_frames,
+ COALESCE_PENDING_LIMIT_UNIT);
+ CHECK_COALESCE_ALIGN(coal->rx_max_coalesced_frames_high,
+ COALESCE_PENDING_LIMIT_UNIT);
+ CHECK_COALESCE_ALIGN(coal->rx_max_coalesced_frames_low,
+ COALESCE_PENDING_LIMIT_UNIT);
+
+ ori_intr_coal = &nic_dev->intr_coalesce[0];
+
+ CHECK_COALESCE_CHANGED(coal->rx_coalesce_usecs, COALESCE_TIMER_CFG_UNIT,
+ ori_intr_coal->coalesce_timer_cfg, obj_str);
+ CHECK_COALESCE_CHANGED(coal->rx_max_coalesced_frames,
+ COALESCE_PENDING_LIMIT_UNIT,
+ ori_intr_coal->pending_limit, obj_str);
+ CHECK_COALESCE_CHANGED(coal->rx_max_coalesced_frames_high,
+ COALESCE_PENDING_LIMIT_UNIT,
+ ori_intr_coal->rx_pending_limit_high, obj_str);
+ CHECK_COALESCE_CHANGED(coal->rx_max_coalesced_frames_low,
+ COALESCE_PENDING_LIMIT_UNIT,
+ ori_intr_coal->rx_pending_limit_low, obj_str);
+
+ old_adaptive = READ_ONCE(nic_dev->adaptive_rx_coal);
+ new_adaptive = !!coal->use_adaptive_rx_coalesce;
+
+ if (old_adaptive) {
+ WRITE_ONCE(nic_dev->adaptive_rx_coal, false);
+
+ synchronize_net();
+
+ for (i = 0; i < nic_dev->q_params.num_qps; i++) {
+ rxq = nic_dev->q_params.irq_cfg[i].rxq;
+ if (!rxq)
+ continue;
+
+ cancel_work_sync(&rxq->dim.work);
+
+ rxq->dim.state = DIM_START_MEASURE;
+ }
+
+ dim_stopped = true;
+ }
+
+ intr_coal.coalesce_timer_cfg =
+ (u8)(coal->rx_coalesce_usecs / COALESCE_TIMER_CFG_UNIT);
+
+ intr_coal.pending_limit =
+ (u8)(coal->rx_max_coalesced_frames /
+ COALESCE_PENDING_LIMIT_UNIT);
+
+ intr_coal.rx_pending_limit_high =
+ (u8)(coal->rx_max_coalesced_frames_high /
+ COALESCE_PENDING_LIMIT_UNIT);
+
+ intr_coal.rx_pending_limit_low =
+ (u8)(coal->rx_max_coalesced_frames_low /
+ COALESCE_PENDING_LIMIT_UNIT);
+
+ /* coalesce timer or pending set to zero will disable coalesce */
+ if (!new_adaptive &&
+ (!intr_coal.coalesce_timer_cfg || !intr_coal.pending_limit))
+ netdev_info(netdev, "Coalesce will be disabled\n");
+
+ err = hinic3_set_hw_coal_param(netdev, &intr_coal, extack);
+ if (err) {
+ if (dim_stopped)
+ WRITE_ONCE(nic_dev->adaptive_rx_coal, old_adaptive);
+
+ return err;
+ }
+
+ WRITE_ONCE(nic_dev->adaptive_rx_coal, new_adaptive);
+
+ return 0;
+}
+
static const struct ethtool_ops hinic3_ethtool_ops = {
- .supported_coalesce_params = ETHTOOL_COALESCE_USECS |
- ETHTOOL_COALESCE_PKT_RATE_RX_USECS,
+ .supported_coalesce_params = ETHTOOL_COALESCE_RX_USECS |
+ ETHTOOL_COALESCE_RX_MAX_FRAMES |
+ ETHTOOL_COALESCE_USE_ADAPTIVE_RX |
+ ETHTOOL_COALESCE_RX_MAX_FRAMES_LOW |
+ ETHTOOL_COALESCE_RX_MAX_FRAMES_HIGH,
.get_link_ksettings = hinic3_get_link_ksettings,
.get_drvinfo = hinic3_get_drvinfo,
.get_msglevel = hinic3_get_msglevel,
@@ -991,6 +1271,8 @@ static const struct ethtool_ops hinic3_ethtool_ops = {
.get_eth_ctrl_stats = hinic3_get_eth_ctrl_stats,
.get_rmon_stats = hinic3_get_rmon_stats,
.get_pause_stats = hinic3_get_pause_stats,
+ .get_coalesce = hinic3_get_coalesce,
+ .set_coalesce = hinic3_set_coalesce,
};
void hinic3_set_ethtool_ops(struct net_device *netdev)
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_irq.c b/drivers/net/ethernet/huawei/hinic3/hinic3_irq.c
index e7d6c2033b45..94d0773ebf2c 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_irq.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_irq.c
@@ -20,7 +20,7 @@ static void hinic3_net_dim(struct hinic3_nic_dev *nic_dev,
struct dim_sample sample = {};
if (!test_bit(HINIC3_INTF_UP, &nic_dev->flags) ||
- !nic_dev->adaptive_rx_coal)
+ !READ_ONCE(nic_dev->adaptive_rx_coal))
return;
dim_update_sample(irq_cfg->total_events, rxq->rxq_stats.packets,
@@ -105,6 +105,7 @@ static int hinic3_request_irq(struct hinic3_irq_cfg *irq_cfg, u16 q_id)
info.coalesc_timer_cfg =
nic_dev->intr_coalesce[q_id].coalesce_timer_cfg;
info.resend_timer_cfg = nic_dev->intr_coalesce[q_id].resend_timer_cfg;
+
err = hinic3_set_interrupt_cfg(nic_dev->hwdev, info);
if (err) {
netdev_err(netdev, "Failed to set RX interrupt coalescing attribute.\n");
@@ -162,17 +163,20 @@ static int hinic3_set_interrupt_moder(struct net_device *netdev, u16 q_id,
static void hinic3_update_queue_coal(struct net_device *netdev, u16 q_id,
u16 coal_timer, u16 coal_pkts)
{
+ u8 coalesc_timer_cfg, pending_limit, limit_low, limit_high;
struct hinic3_intr_coal_info *q_coal;
- u8 coalesc_timer_cfg, pending_limit;
struct hinic3_nic_dev *nic_dev;
nic_dev = netdev_priv(netdev);
q_coal = &nic_dev->intr_coalesce[q_id];
coalesc_timer_cfg = (u8)coal_timer;
+
+ limit_low = q_coal->rx_pending_limit_low;
+ limit_high = q_coal->rx_pending_limit_high;
+
pending_limit = clamp_t(u8, coal_pkts >> HINIC3_COAL_PKT_SHIFT,
- q_coal->rx_pending_limit_low,
- q_coal->rx_pending_limit_high);
+ limit_low, limit_high);
hinic3_set_interrupt_moder(nic_dev->netdev, q_id,
coalesc_timer_cfg, pending_limit);
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_main.c b/drivers/net/ethernet/huawei/hinic3/hinic3_main.c
index 1e7ac3f330d7..a0618e6dc219 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_main.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_main.c
@@ -51,7 +51,7 @@ static void init_intr_coal_param(struct net_device *netdev)
info->rx_pending_limit_low = HINIC3_RX_PENDING_LIMIT_LOW;
}
- nic_dev->adaptive_rx_coal = 1;
+ WRITE_ONCE(nic_dev->adaptive_rx_coal, 1);
}
static int hinic3_init_intr_coalesce(struct net_device *netdev)
--
2.54.0
^ permalink raw reply related
* [PATCH net-next v11 2/5] hinic3: Add ethtool statistic ops
From: Fan Gong @ 2026-07-20 12:53 UTC (permalink / raw)
To: Fan Gong, Wu Di, Teng Peisen, netdev, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Andrew Lunn, Ioana Ciornei, Mohsin Bashir, Dimitri Daskalakis,
Harshitha Ramamurthy
Cc: linux-kernel, linux-doc, luosifu, Xin Guo, Zhou Shuai, Wu Like,
Shi Jing, Zheng Jiezhen, Maxime Chevallier
In-Reply-To: <cover.1784538912.git.root@localhost.localdomain>
Add PF/VF statistics functions in TX and RX processing.
Implement following ethtool callback function:
.get_sset_count
.get_ethtool_stats
.get_strings
.get_eth_phy_stats
.get_eth_mac_stats
.get_eth_ctrl_stats
.get_rmon_stats
.get_pause_stats
These callbacks allow users to utilize ethtool for detailed
TX and RX netdev stats monitoring.
Add mgmt_msg_params_init_in_out() to support management
commands that require separate input and output buffers. This is
needed for retrieving the expanded PF/VF MAC statistics from HW,
which no longer fit in the input buffer used by the existing path.
Remove unused stats "restore_drop_sge" in struct hinic3_rxq_stats.
Co-developed-by: Wu Di <wudi234@huawei.com>
Signed-off-by: Wu Di <wudi234@huawei.com>
Co-developed-by: Teng Peisen <tengpeisen@huawei.com>
Signed-off-by: Teng Peisen <tengpeisen@huawei.com>
Signed-off-by: Fan Gong <gongfan1@huawei.com>
---
.../ethernet/huawei/hinic3/hinic3_ethtool.c | 483 ++++++++++++++++++
.../ethernet/huawei/hinic3/hinic3_hw_intf.h | 11 +
.../huawei/hinic3/hinic3_mgmt_interface.h | 37 ++
.../ethernet/huawei/hinic3/hinic3_nic_cfg.c | 64 +++
.../ethernet/huawei/hinic3/hinic3_nic_cfg.h | 109 ++++
.../net/ethernet/huawei/hinic3/hinic3_rx.c | 69 ++-
.../net/ethernet/huawei/hinic3/hinic3_rx.h | 15 +-
.../net/ethernet/huawei/hinic3/hinic3_tx.c | 62 ++-
.../net/ethernet/huawei/hinic3/hinic3_tx.h | 2 +
9 files changed, 841 insertions(+), 11 deletions(-)
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c b/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c
index e6dff46a0d00..0e1f83806933 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_ethtool.c
@@ -498,6 +498,481 @@ static int hinic3_set_ringparam(struct net_device *netdev,
return 0;
}
+struct hinic3_stats {
+ char name[ETH_GSTRING_LEN];
+ u32 size;
+ int offset;
+};
+
+#define HINIC3_RXQ_STAT(_stat_item) { \
+ .name = "rxq%d_"#_stat_item, \
+ .size = sizeof_field(struct hinic3_rxq_stats, _stat_item), \
+ .offset = offsetof(struct hinic3_rxq_stats, _stat_item) \
+}
+
+#define HINIC3_TXQ_STAT(_stat_item) { \
+ .name = "txq%d_"#_stat_item, \
+ .size = sizeof_field(struct hinic3_txq_stats, _stat_item), \
+ .offset = offsetof(struct hinic3_txq_stats, _stat_item) \
+}
+
+static struct hinic3_stats hinic3_rx_queue_stats[] = {
+ HINIC3_RXQ_STAT(csum_errors),
+ HINIC3_RXQ_STAT(other_errors),
+ HINIC3_RXQ_STAT(rx_buf_empty),
+ HINIC3_RXQ_STAT(alloc_skb_err),
+ HINIC3_RXQ_STAT(alloc_rx_buf_err),
+};
+
+static struct hinic3_stats hinic3_tx_queue_stats[] = {
+ HINIC3_TXQ_STAT(busy),
+ HINIC3_TXQ_STAT(skb_pad_err),
+ HINIC3_TXQ_STAT(frag_len_overflow),
+ HINIC3_TXQ_STAT(offload_cow_skb_err),
+ HINIC3_TXQ_STAT(map_frag_err),
+ HINIC3_TXQ_STAT(unknown_tunnel_pkt),
+ HINIC3_TXQ_STAT(frag_size_err),
+};
+
+#define HINIC3_FUNC_STAT(_stat_item) { \
+ .name = #_stat_item, \
+ .size = sizeof_field(struct l2nic_vport_stats, _stat_item), \
+ .offset = offsetof(struct l2nic_vport_stats, _stat_item) \
+}
+
+static struct hinic3_stats hinic3_function_stats[] = {
+ HINIC3_FUNC_STAT(tx_unicast_pkts_vport),
+ HINIC3_FUNC_STAT(tx_unicast_bytes_vport),
+ HINIC3_FUNC_STAT(tx_multicast_pkts_vport),
+ HINIC3_FUNC_STAT(tx_multicast_bytes_vport),
+ HINIC3_FUNC_STAT(tx_broadcast_pkts_vport),
+ HINIC3_FUNC_STAT(tx_broadcast_bytes_vport),
+
+ HINIC3_FUNC_STAT(rx_unicast_pkts_vport),
+ HINIC3_FUNC_STAT(rx_unicast_bytes_vport),
+ HINIC3_FUNC_STAT(rx_multicast_pkts_vport),
+ HINIC3_FUNC_STAT(rx_multicast_bytes_vport),
+ HINIC3_FUNC_STAT(rx_broadcast_pkts_vport),
+ HINIC3_FUNC_STAT(rx_broadcast_bytes_vport),
+
+ HINIC3_FUNC_STAT(tx_discard_vport),
+ HINIC3_FUNC_STAT(rx_discard_vport),
+ HINIC3_FUNC_STAT(tx_err_vport),
+ HINIC3_FUNC_STAT(rx_err_vport),
+};
+
+#define HINIC3_PORT_STAT(_stat_item) { \
+ .name = #_stat_item, \
+ .size = sizeof_field(struct mag_cmd_port_stats, _stat_item), \
+ .offset = offsetof(struct mag_cmd_port_stats, _stat_item) \
+}
+
+static struct hinic3_stats hinic3_port_stats[] = {
+ HINIC3_PORT_STAT(mac_tx_fragment_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_undersize_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_undermin_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_1519_max_bad_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_1519_max_good_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_oversize_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_jabber_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_bad_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_bad_oct_num),
+ HINIC3_PORT_STAT(mac_tx_good_oct_num),
+ HINIC3_PORT_STAT(mac_tx_total_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_uni_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_pfc_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_pfc_pri0_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_pfc_pri1_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_pfc_pri2_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_pfc_pri3_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_pfc_pri4_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_pfc_pri5_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_pfc_pri6_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_pfc_pri7_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_err_all_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_from_app_good_pkt_num),
+ HINIC3_PORT_STAT(mac_tx_from_app_bad_pkt_num),
+
+ HINIC3_PORT_STAT(mac_rx_undermin_pkt_num),
+ HINIC3_PORT_STAT(mac_rx_1519_max_bad_pkt_num),
+ HINIC3_PORT_STAT(mac_rx_1519_max_good_pkt_num),
+ HINIC3_PORT_STAT(mac_rx_bad_pkt_num),
+ HINIC3_PORT_STAT(mac_rx_bad_oct_num),
+ HINIC3_PORT_STAT(mac_rx_good_oct_num),
+ HINIC3_PORT_STAT(mac_rx_total_pkt_num),
+ HINIC3_PORT_STAT(mac_rx_uni_pkt_num),
+ HINIC3_PORT_STAT(mac_rx_pfc_pkt_num),
+ HINIC3_PORT_STAT(mac_rx_pfc_pri0_pkt_num),
+ HINIC3_PORT_STAT(mac_rx_pfc_pri1_pkt_num),
+ HINIC3_PORT_STAT(mac_rx_pfc_pri2_pkt_num),
+ HINIC3_PORT_STAT(mac_rx_pfc_pri3_pkt_num),
+ HINIC3_PORT_STAT(mac_rx_pfc_pri4_pkt_num),
+ HINIC3_PORT_STAT(mac_rx_pfc_pri5_pkt_num),
+ HINIC3_PORT_STAT(mac_rx_pfc_pri6_pkt_num),
+ HINIC3_PORT_STAT(mac_rx_pfc_pri7_pkt_num),
+ HINIC3_PORT_STAT(mac_rx_send_app_good_pkt_num),
+ HINIC3_PORT_STAT(mac_rx_send_app_bad_pkt_num),
+ HINIC3_PORT_STAT(mac_rx_unfilter_pkt_num),
+};
+
+static int hinic3_get_sset_count(struct net_device *netdev, int sset)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ int count, q_num;
+
+ switch (sset) {
+ case ETH_SS_STATS:
+ q_num = nic_dev->q_params.num_qps;
+ count = ARRAY_SIZE(hinic3_function_stats) +
+ (ARRAY_SIZE(hinic3_tx_queue_stats) +
+ ARRAY_SIZE(hinic3_rx_queue_stats)) *
+ q_num;
+
+ if (!HINIC3_IS_VF(nic_dev->hwdev))
+ count += ARRAY_SIZE(hinic3_port_stats);
+
+ return count;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static u64 get_val_of_ptr(u32 size, const void *ptr)
+{
+ u64 ret = size == sizeof(u64) ? *(u64 *)ptr :
+ size == sizeof(u32) ? *(u32 *)ptr :
+ size == sizeof(u16) ? *(u16 *)ptr :
+ *(u8 *)ptr;
+
+ return ret;
+}
+
+static void hinic3_get_drv_queue_stats(struct net_device *netdev, u64 *data)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ struct hinic3_txq_stats txq_stats = {};
+ struct hinic3_rxq_stats rxq_stats = {};
+ u16 i = 0, j, qid;
+ char *p;
+
+ for (qid = 0; qid < nic_dev->q_params.num_qps; qid++) {
+ if (!nic_dev->txqs)
+ break;
+
+ hinic3_txq_get_stats(&nic_dev->txqs[qid], &txq_stats);
+ for (j = 0; j < ARRAY_SIZE(hinic3_tx_queue_stats); j++, i++) {
+ p = (char *)&txq_stats +
+ hinic3_tx_queue_stats[j].offset;
+ data[i] = get_val_of_ptr(hinic3_tx_queue_stats[j].size,
+ p);
+ }
+ }
+
+ i = nic_dev->q_params.num_qps * ARRAY_SIZE(hinic3_tx_queue_stats);
+ for (qid = 0; qid < nic_dev->q_params.num_qps; qid++) {
+ if (!nic_dev->rxqs)
+ break;
+
+ hinic3_rxq_get_stats(&nic_dev->rxqs[qid], &rxq_stats);
+ for (j = 0; j < ARRAY_SIZE(hinic3_rx_queue_stats); j++, i++) {
+ p = (char *)&rxq_stats +
+ hinic3_rx_queue_stats[j].offset;
+ data[i] = get_val_of_ptr(hinic3_rx_queue_stats[j].size,
+ p);
+ }
+ }
+}
+
+static u16 hinic3_get_ethtool_port_stats(struct net_device *netdev, u64 *data)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ struct mag_cmd_port_stats *ps;
+ u16 i = 0, j;
+ char *p;
+ int err;
+
+ ps = kmalloc_obj(*ps);
+ if (!ps)
+ goto err_zero_stats;
+
+ err = hinic3_get_phy_port_stats(nic_dev->hwdev, ps);
+ if (err) {
+ kfree(ps);
+ netdev_err(netdev, "Failed to get port stats from fw\n");
+ goto err_zero_stats;
+ }
+
+ for (j = 0; j < ARRAY_SIZE(hinic3_port_stats); j++, i++) {
+ p = (char *)ps + hinic3_port_stats[j].offset;
+ data[i] = get_val_of_ptr(hinic3_port_stats[j].size, p);
+ }
+
+ kfree(ps);
+
+ return i;
+
+err_zero_stats:
+ memset(&data[i], 0, ARRAY_SIZE(hinic3_port_stats) * sizeof(*data));
+
+ return i + ARRAY_SIZE(hinic3_port_stats);
+}
+
+static void hinic3_get_ethtool_stats(struct net_device *netdev,
+ struct ethtool_stats *stats, u64 *data)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ struct l2nic_vport_stats vport_stats = {};
+ u16 i = 0, j;
+ char *p;
+ int err;
+
+ err = hinic3_get_vport_stats(nic_dev->hwdev,
+ hinic3_global_func_id(nic_dev->hwdev),
+ &vport_stats);
+ if (err)
+ netdev_err(netdev, "Failed to get function stats from fw\n");
+
+ for (j = 0; j < ARRAY_SIZE(hinic3_function_stats); j++, i++) {
+ p = (char *)&vport_stats + hinic3_function_stats[j].offset;
+ data[i] = get_val_of_ptr(hinic3_function_stats[j].size, p);
+ }
+
+ if (!HINIC3_IS_VF(nic_dev->hwdev))
+ i += hinic3_get_ethtool_port_stats(netdev, data + i);
+
+ hinic3_get_drv_queue_stats(netdev, data + i);
+}
+
+static u16 hinic3_get_hw_stats_strings(struct net_device *netdev, char *p)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ u16 i, cnt = 0;
+
+ for (i = 0; i < ARRAY_SIZE(hinic3_function_stats); i++) {
+ memcpy(p, hinic3_function_stats[i].name, ETH_GSTRING_LEN);
+ p += ETH_GSTRING_LEN;
+ cnt++;
+ }
+
+ if (!HINIC3_IS_VF(nic_dev->hwdev)) {
+ for (i = 0; i < ARRAY_SIZE(hinic3_port_stats); i++) {
+ memcpy(p, hinic3_port_stats[i].name, ETH_GSTRING_LEN);
+ p += ETH_GSTRING_LEN;
+ cnt++;
+ }
+ }
+
+ return cnt;
+}
+
+static void hinic3_get_qp_stats_strings(struct net_device *netdev, char *p)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ u8 *data = p;
+ u16 i, j;
+
+ for (i = 0; i < nic_dev->q_params.num_qps; i++) {
+ for (j = 0; j < ARRAY_SIZE(hinic3_tx_queue_stats); j++)
+ ethtool_sprintf(&data,
+ hinic3_tx_queue_stats[j].name, i);
+ }
+
+ for (i = 0; i < nic_dev->q_params.num_qps; i++) {
+ for (j = 0; j < ARRAY_SIZE(hinic3_rx_queue_stats); j++)
+ ethtool_sprintf(&data,
+ hinic3_rx_queue_stats[j].name, i);
+ }
+}
+
+static void hinic3_get_strings(struct net_device *netdev,
+ u32 stringset, u8 *data)
+{
+ char *p = (char *)data;
+ u16 offset;
+
+ switch (stringset) {
+ case ETH_SS_STATS:
+ offset = hinic3_get_hw_stats_strings(netdev, p);
+ hinic3_get_qp_stats_strings(netdev,
+ p + offset * ETH_GSTRING_LEN);
+
+ return;
+ default:
+ netdev_err(netdev, "Invalid string set %u.\n", stringset);
+ return;
+ }
+}
+
+static void hinic3_get_eth_phy_stats(struct net_device *netdev,
+ struct ethtool_eth_phy_stats *phy_stats)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ struct mag_cmd_port_stats *ps;
+ int err;
+
+ ps = kmalloc_obj(*ps);
+ if (!ps)
+ return;
+
+ err = hinic3_get_phy_port_stats(nic_dev->hwdev, ps);
+ if (err) {
+ kfree(ps);
+ netdev_err(netdev, "Failed to get eth phy stats from fw\n");
+ return;
+ }
+
+ phy_stats->SymbolErrorDuringCarrier = ps->mac_rx_sym_err_pkt_num;
+
+ kfree(ps);
+}
+
+static void hinic3_get_eth_mac_stats(struct net_device *netdev,
+ struct ethtool_eth_mac_stats *mac_stats)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ struct mag_cmd_port_stats *ps;
+ int err;
+
+ ps = kmalloc_obj(*ps);
+ if (!ps)
+ return;
+
+ err = hinic3_get_phy_port_stats(nic_dev->hwdev, ps);
+ if (err) {
+ kfree(ps);
+ netdev_err(netdev, "Failed to get eth mac stats from fw\n");
+ return;
+ }
+
+ mac_stats->FramesTransmittedOK = ps->mac_tx_good_pkt_num;
+ mac_stats->FramesReceivedOK = ps->mac_rx_good_pkt_num;
+ mac_stats->FrameCheckSequenceErrors = ps->mac_rx_fcs_err_pkt_num;
+ mac_stats->OctetsTransmittedOK = ps->mac_tx_total_oct_num;
+ mac_stats->OctetsReceivedOK = ps->mac_rx_total_oct_num;
+ mac_stats->MulticastFramesXmittedOK = ps->mac_tx_multi_pkt_num;
+ mac_stats->BroadcastFramesXmittedOK = ps->mac_tx_broad_pkt_num;
+ mac_stats->MulticastFramesReceivedOK = ps->mac_rx_multi_pkt_num;
+ mac_stats->BroadcastFramesReceivedOK = ps->mac_rx_broad_pkt_num;
+
+ kfree(ps);
+}
+
+static void hinic3_get_eth_ctrl_stats(struct net_device *netdev,
+ struct ethtool_eth_ctrl_stats *ctrl_stats)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ struct mag_cmd_port_stats *ps;
+ int err;
+
+ ps = kmalloc_obj(*ps);
+ if (!ps)
+ return;
+
+ err = hinic3_get_phy_port_stats(nic_dev->hwdev, ps);
+ if (err) {
+ kfree(ps);
+ netdev_err(netdev, "Failed to get eth ctrl stats from fw\n");
+ return;
+ }
+
+ ctrl_stats->MACControlFramesTransmitted = ps->mac_tx_control_pkt_num;
+ ctrl_stats->MACControlFramesReceived = ps->mac_rx_control_pkt_num;
+
+ kfree(ps);
+}
+
+static const struct ethtool_rmon_hist_range hinic3_rmon_ranges[] = {
+ { 0, 64 },
+ { 65, 127 },
+ { 128, 255 },
+ { 256, 511 },
+ { 512, 1023 },
+ { 1024, 1518 },
+ { 1519, 2047 },
+ { 2048, 4095 },
+ { 4096, 8191 },
+ { 8192, 9216 },
+ { 9217, 12287 },
+ {}
+};
+
+static void hinic3_get_rmon_stats(struct net_device *netdev,
+ struct ethtool_rmon_stats *rmon_stats,
+ const struct ethtool_rmon_hist_range **ranges)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ struct mag_cmd_port_stats *ps;
+ int err;
+
+ ps = kmalloc_obj(*ps);
+ if (!ps)
+ return;
+
+ err = hinic3_get_phy_port_stats(nic_dev->hwdev, ps);
+ if (err) {
+ kfree(ps);
+ netdev_err(netdev, "Failed to get eth rmon stats from fw\n");
+ return;
+ }
+
+ rmon_stats->undersize_pkts = ps->mac_rx_undersize_pkt_num;
+ rmon_stats->oversize_pkts = ps->mac_rx_oversize_pkt_num;
+ rmon_stats->fragments = ps->mac_rx_fragment_pkt_num;
+ rmon_stats->jabbers = ps->mac_rx_jabber_pkt_num;
+
+ rmon_stats->hist[0] = ps->mac_rx_64_oct_pkt_num;
+ rmon_stats->hist[1] = ps->mac_rx_65_127_oct_pkt_num;
+ rmon_stats->hist[2] = ps->mac_rx_128_255_oct_pkt_num;
+ rmon_stats->hist[3] = ps->mac_rx_256_511_oct_pkt_num;
+ rmon_stats->hist[4] = ps->mac_rx_512_1023_oct_pkt_num;
+ rmon_stats->hist[5] = ps->mac_rx_1024_1518_oct_pkt_num;
+ rmon_stats->hist[6] = ps->mac_rx_1519_2047_oct_pkt_num;
+ rmon_stats->hist[7] = ps->mac_rx_2048_4095_oct_pkt_num;
+ rmon_stats->hist[8] = ps->mac_rx_4096_8191_oct_pkt_num;
+ rmon_stats->hist[9] = ps->mac_rx_8192_9216_oct_pkt_num;
+ rmon_stats->hist[10] = ps->mac_rx_9217_12287_oct_pkt_num;
+
+ rmon_stats->hist_tx[0] = ps->mac_tx_64_oct_pkt_num;
+ rmon_stats->hist_tx[1] = ps->mac_tx_65_127_oct_pkt_num;
+ rmon_stats->hist_tx[2] = ps->mac_tx_128_255_oct_pkt_num;
+ rmon_stats->hist_tx[3] = ps->mac_tx_256_511_oct_pkt_num;
+ rmon_stats->hist_tx[4] = ps->mac_tx_512_1023_oct_pkt_num;
+ rmon_stats->hist_tx[5] = ps->mac_tx_1024_1518_oct_pkt_num;
+ rmon_stats->hist_tx[6] = ps->mac_tx_1519_2047_oct_pkt_num;
+ rmon_stats->hist_tx[7] = ps->mac_tx_2048_4095_oct_pkt_num;
+ rmon_stats->hist_tx[8] = ps->mac_tx_4096_8191_oct_pkt_num;
+ rmon_stats->hist_tx[9] = ps->mac_tx_8192_9216_oct_pkt_num;
+ rmon_stats->hist_tx[10] = ps->mac_tx_9217_12287_oct_pkt_num;
+
+ *ranges = hinic3_rmon_ranges;
+
+ kfree(ps);
+}
+
+static void hinic3_get_pause_stats(struct net_device *netdev,
+ struct ethtool_pause_stats *pause_stats)
+{
+ struct hinic3_nic_dev *nic_dev = netdev_priv(netdev);
+ struct mag_cmd_port_stats *ps;
+ int err;
+
+ ps = kmalloc_obj(*ps);
+ if (!ps)
+ return;
+
+ err = hinic3_get_phy_port_stats(nic_dev->hwdev, ps);
+ if (err) {
+ kfree(ps);
+ netdev_err(netdev, "Failed to get eth pause stats from fw\n");
+ return;
+ }
+
+ pause_stats->tx_pause_frames = ps->mac_tx_pause_num;
+ pause_stats->rx_pause_frames = ps->mac_rx_pause_num;
+
+ kfree(ps);
+}
+
static const struct ethtool_ops hinic3_ethtool_ops = {
.supported_coalesce_params = ETHTOOL_COALESCE_USECS |
ETHTOOL_COALESCE_PKT_RATE_RX_USECS,
@@ -508,6 +983,14 @@ static const struct ethtool_ops hinic3_ethtool_ops = {
.get_link = ethtool_op_get_link,
.get_ringparam = hinic3_get_ringparam,
.set_ringparam = hinic3_set_ringparam,
+ .get_sset_count = hinic3_get_sset_count,
+ .get_ethtool_stats = hinic3_get_ethtool_stats,
+ .get_strings = hinic3_get_strings,
+ .get_eth_phy_stats = hinic3_get_eth_phy_stats,
+ .get_eth_mac_stats = hinic3_get_eth_mac_stats,
+ .get_eth_ctrl_stats = hinic3_get_eth_ctrl_stats,
+ .get_rmon_stats = hinic3_get_rmon_stats,
+ .get_pause_stats = hinic3_get_pause_stats,
};
void hinic3_set_ethtool_ops(struct net_device *netdev)
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_hw_intf.h b/drivers/net/ethernet/huawei/hinic3/hinic3_hw_intf.h
index cfc9daa3034f..6b0f486ba590 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_hw_intf.h
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_hw_intf.h
@@ -53,6 +53,17 @@ static inline void mgmt_msg_params_init_default(struct mgmt_msg_params *msg_para
msg_params->timeout_ms = 0;
}
+static inline void
+mgmt_msg_params_init_in_out(struct mgmt_msg_params *msg_params, void *in_buf,
+ void *out_buf, u32 in_buf_size, u32 out_buf_size)
+{
+ msg_params->buf_in = in_buf;
+ msg_params->buf_out = out_buf;
+ msg_params->in_size = in_buf_size;
+ msg_params->expected_out_size = out_buf_size;
+ msg_params->timeout_ms = 0;
+}
+
enum cfg_cmd {
CFG_CMD_GET_DEV_CAP = 0,
};
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_mgmt_interface.h b/drivers/net/ethernet/huawei/hinic3/hinic3_mgmt_interface.h
index c5bca3c4af96..76c691f82703 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_mgmt_interface.h
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_mgmt_interface.h
@@ -143,6 +143,41 @@ struct l2nic_cmd_set_dcb_state {
u8 rsvd[7];
};
+struct l2nic_port_stats_info {
+ struct mgmt_msg_head msg_head;
+ u16 func_id;
+ u16 rsvd1;
+};
+
+struct l2nic_vport_stats {
+ u64 tx_unicast_pkts_vport;
+ u64 tx_unicast_bytes_vport;
+ u64 tx_multicast_pkts_vport;
+ u64 tx_multicast_bytes_vport;
+ u64 tx_broadcast_pkts_vport;
+ u64 tx_broadcast_bytes_vport;
+
+ u64 rx_unicast_pkts_vport;
+ u64 rx_unicast_bytes_vport;
+ u64 rx_multicast_pkts_vport;
+ u64 rx_multicast_bytes_vport;
+ u64 rx_broadcast_pkts_vport;
+ u64 rx_broadcast_bytes_vport;
+
+ u64 tx_discard_vport;
+ u64 rx_discard_vport;
+ u64 tx_err_vport;
+ u64 rx_err_vport;
+};
+
+struct l2nic_cmd_vport_stats {
+ struct mgmt_msg_head msg_head;
+ u32 stats_size;
+ u32 rsvd1;
+ struct l2nic_vport_stats stats;
+ u64 rsvd2[6];
+};
+
struct l2nic_cmd_lro_config {
struct mgmt_msg_head msg_head;
u16 func_id;
@@ -234,6 +269,7 @@ enum l2nic_cmd {
L2NIC_CMD_SET_VPORT_ENABLE = 6,
L2NIC_CMD_SET_RX_MODE = 7,
L2NIC_CMD_SET_SQ_CI_ATTR = 8,
+ L2NIC_CMD_GET_VPORT_STAT = 9,
L2NIC_CMD_CLEAR_QP_RESOURCE = 11,
L2NIC_CMD_CFG_RX_LRO = 13,
L2NIC_CMD_CFG_LRO_TIMER = 14,
@@ -272,6 +308,7 @@ enum mag_cmd {
MAG_CMD_SET_PORT_ENABLE = 6,
MAG_CMD_GET_LINK_STATUS = 7,
+ MAG_CMD_GET_PORT_STAT = 151,
MAG_CMD_GET_PORT_INFO = 153,
};
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_nic_cfg.c b/drivers/net/ethernet/huawei/hinic3/hinic3_nic_cfg.c
index de5a7984d2cb..1b14dc824ce1 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_nic_cfg.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_nic_cfg.c
@@ -639,6 +639,42 @@ int hinic3_get_link_status(struct hinic3_hwdev *hwdev, bool *link_status_up)
return 0;
}
+int hinic3_get_phy_port_stats(struct hinic3_hwdev *hwdev,
+ struct mag_cmd_port_stats *stats)
+{
+ struct mag_cmd_port_stats_info stats_info = {};
+ struct mag_cmd_get_port_stat *ps;
+ struct mgmt_msg_params msg_params = {};
+ int err;
+
+ ps = kzalloc_obj(*ps);
+ if (!ps)
+ return -ENOMEM;
+
+ stats_info.port_id = hinic3_physical_port_id(hwdev);
+
+ mgmt_msg_params_init_in_out(&msg_params, &stats_info, ps,
+ sizeof(stats_info), sizeof(*ps));
+
+ err = hinic3_send_mbox_to_mgmt(hwdev, MGMT_MOD_HILINK,
+ MAG_CMD_GET_PORT_STAT, &msg_params);
+
+ if (err || ps->head.status) {
+ dev_err(hwdev->dev,
+ "Failed to get port statistics, err: %d, status: 0x%x\n",
+ err, ps->head.status);
+ err = -EFAULT;
+ goto out;
+ }
+
+ memcpy(stats, &ps->counter, sizeof(*stats));
+
+out:
+ kfree(ps);
+
+ return err;
+}
+
int hinic3_get_port_info(struct hinic3_hwdev *hwdev,
struct hinic3_nic_port_info *port_info)
{
@@ -738,3 +774,31 @@ int hinic3_get_pause_info(struct hinic3_nic_dev *nic_dev,
return hinic3_cfg_hw_pause(nic_dev->hwdev, MGMT_MSG_CMD_OP_GET,
nic_pause);
}
+
+int hinic3_get_vport_stats(struct hinic3_hwdev *hwdev, u16 func_id,
+ struct l2nic_vport_stats *stats)
+{
+ struct l2nic_cmd_vport_stats vport_stats = {};
+ struct l2nic_port_stats_info stats_info = {};
+ struct mgmt_msg_params msg_params = {};
+ int err;
+
+ stats_info.func_id = func_id;
+
+ mgmt_msg_params_init_in_out(&msg_params, &stats_info, &vport_stats,
+ sizeof(stats_info), sizeof(vport_stats));
+
+ err = hinic3_send_mbox_to_mgmt(hwdev, MGMT_MOD_L2NIC,
+ L2NIC_CMD_GET_VPORT_STAT, &msg_params);
+
+ if (err || vport_stats.msg_head.status) {
+ dev_err(hwdev->dev,
+ "Failed to get function statistics, err: %d, status: 0x%x\n",
+ err, vport_stats.msg_head.status);
+ return -EFAULT;
+ }
+
+ memcpy(stats, &vport_stats.stats, sizeof(*stats));
+
+ return 0;
+}
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_nic_cfg.h b/drivers/net/ethernet/huawei/hinic3/hinic3_nic_cfg.h
index 5d52202a8d4e..80573c121539 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_nic_cfg.h
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_nic_cfg.h
@@ -129,6 +129,110 @@ struct mag_cmd_get_xsfp_present {
u8 rsvd[2];
};
+struct mag_cmd_port_stats {
+ u64 mac_tx_fragment_pkt_num;
+ u64 mac_tx_undersize_pkt_num;
+ u64 mac_tx_undermin_pkt_num;
+ u64 mac_tx_64_oct_pkt_num;
+ u64 mac_tx_65_127_oct_pkt_num;
+ u64 mac_tx_128_255_oct_pkt_num;
+ u64 mac_tx_256_511_oct_pkt_num;
+ u64 mac_tx_512_1023_oct_pkt_num;
+ u64 mac_tx_1024_1518_oct_pkt_num;
+ u64 mac_tx_1519_2047_oct_pkt_num;
+ u64 mac_tx_2048_4095_oct_pkt_num;
+ u64 mac_tx_4096_8191_oct_pkt_num;
+ u64 mac_tx_8192_9216_oct_pkt_num;
+ u64 mac_tx_9217_12287_oct_pkt_num;
+ u64 mac_tx_12288_16383_oct_pkt_num;
+ u64 mac_tx_1519_max_bad_pkt_num;
+ u64 mac_tx_1519_max_good_pkt_num;
+ u64 mac_tx_oversize_pkt_num;
+ u64 mac_tx_jabber_pkt_num;
+ u64 mac_tx_bad_pkt_num;
+ u64 mac_tx_bad_oct_num;
+ u64 mac_tx_good_pkt_num;
+ u64 mac_tx_good_oct_num;
+ u64 mac_tx_total_pkt_num;
+ u64 mac_tx_total_oct_num;
+ u64 mac_tx_uni_pkt_num;
+ u64 mac_tx_multi_pkt_num;
+ u64 mac_tx_broad_pkt_num;
+ u64 mac_tx_pause_num;
+ u64 mac_tx_pfc_pkt_num;
+ u64 mac_tx_pfc_pri0_pkt_num;
+ u64 mac_tx_pfc_pri1_pkt_num;
+ u64 mac_tx_pfc_pri2_pkt_num;
+ u64 mac_tx_pfc_pri3_pkt_num;
+ u64 mac_tx_pfc_pri4_pkt_num;
+ u64 mac_tx_pfc_pri5_pkt_num;
+ u64 mac_tx_pfc_pri6_pkt_num;
+ u64 mac_tx_pfc_pri7_pkt_num;
+ u64 mac_tx_control_pkt_num;
+ u64 mac_tx_err_all_pkt_num;
+ u64 mac_tx_from_app_good_pkt_num;
+ u64 mac_tx_from_app_bad_pkt_num;
+
+ u64 mac_rx_fragment_pkt_num;
+ u64 mac_rx_undersize_pkt_num;
+ u64 mac_rx_undermin_pkt_num;
+ u64 mac_rx_64_oct_pkt_num;
+ u64 mac_rx_65_127_oct_pkt_num;
+ u64 mac_rx_128_255_oct_pkt_num;
+ u64 mac_rx_256_511_oct_pkt_num;
+ u64 mac_rx_512_1023_oct_pkt_num;
+ u64 mac_rx_1024_1518_oct_pkt_num;
+ u64 mac_rx_1519_2047_oct_pkt_num;
+ u64 mac_rx_2048_4095_oct_pkt_num;
+ u64 mac_rx_4096_8191_oct_pkt_num;
+ u64 mac_rx_8192_9216_oct_pkt_num;
+ u64 mac_rx_9217_12287_oct_pkt_num;
+ u64 mac_rx_12288_16383_oct_pkt_num;
+ u64 mac_rx_1519_max_bad_pkt_num;
+ u64 mac_rx_1519_max_good_pkt_num;
+ u64 mac_rx_oversize_pkt_num;
+ u64 mac_rx_jabber_pkt_num;
+ u64 mac_rx_bad_pkt_num;
+ u64 mac_rx_bad_oct_num;
+ u64 mac_rx_good_pkt_num;
+ u64 mac_rx_good_oct_num;
+ u64 mac_rx_total_pkt_num;
+ u64 mac_rx_total_oct_num;
+ u64 mac_rx_uni_pkt_num;
+ u64 mac_rx_multi_pkt_num;
+ u64 mac_rx_broad_pkt_num;
+ u64 mac_rx_pause_num;
+ u64 mac_rx_pfc_pkt_num;
+ u64 mac_rx_pfc_pri0_pkt_num;
+ u64 mac_rx_pfc_pri1_pkt_num;
+ u64 mac_rx_pfc_pri2_pkt_num;
+ u64 mac_rx_pfc_pri3_pkt_num;
+ u64 mac_rx_pfc_pri4_pkt_num;
+ u64 mac_rx_pfc_pri5_pkt_num;
+ u64 mac_rx_pfc_pri6_pkt_num;
+ u64 mac_rx_pfc_pri7_pkt_num;
+ u64 mac_rx_control_pkt_num;
+ u64 mac_rx_sym_err_pkt_num;
+ u64 mac_rx_fcs_err_pkt_num;
+ u64 mac_rx_send_app_good_pkt_num;
+ u64 mac_rx_send_app_bad_pkt_num;
+ u64 mac_rx_unfilter_pkt_num;
+};
+
+struct mag_cmd_port_stats_info {
+ struct mgmt_msg_head head;
+
+ u8 port_id;
+ u8 rsvd0[3];
+};
+
+struct mag_cmd_get_port_stat {
+ struct mgmt_msg_head head;
+
+ struct mag_cmd_port_stats counter;
+ u64 rsvd1[15];
+};
+
enum link_err_type {
LINK_ERR_MODULE_UNRECOGENIZED,
LINK_ERR_NUM,
@@ -209,6 +313,11 @@ int hinic3_get_port_info(struct hinic3_hwdev *hwdev,
struct hinic3_nic_port_info *port_info);
int hinic3_set_vport_enable(struct hinic3_hwdev *hwdev, u16 func_id,
bool enable);
+int hinic3_get_phy_port_stats(struct hinic3_hwdev *hwdev,
+ struct mag_cmd_port_stats *stats);
+int hinic3_get_vport_stats(struct hinic3_hwdev *hwdev, u16 func_id,
+ struct l2nic_vport_stats *stats);
+
int hinic3_add_vlan(struct hinic3_hwdev *hwdev, u16 vlan_id, u16 func_id);
int hinic3_del_vlan(struct hinic3_hwdev *hwdev, u16 vlan_id, u16 func_id);
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_rx.c b/drivers/net/ethernet/huawei/hinic3/hinic3_rx.c
index 389b1c2158be..a83a40202e35 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_rx.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_rx.c
@@ -28,10 +28,6 @@
#define HINIC3_LRO_PKT_HDR_LEN_IPV4 66
#define HINIC3_LRO_PKT_HDR_LEN_IPV6 86
-#define HINIC3_LRO_PKT_HDR_LEN(cqe) \
- (RQ_CQE_OFFOLAD_TYPE_GET((cqe)->offload_type, IP_TYPE) == \
- HINIC3_RX_IPV6_PKT ? HINIC3_LRO_PKT_HDR_LEN_IPV6 : \
- HINIC3_LRO_PKT_HDR_LEN_IPV4)
static void hinic3_rxq_clean_stats(struct hinic3_rxq_stats *rxq_stats)
{
@@ -46,7 +42,6 @@ static void hinic3_rxq_clean_stats(struct hinic3_rxq_stats *rxq_stats)
rxq_stats->alloc_skb_err = 0;
rxq_stats->alloc_rx_buf_err = 0;
- rxq_stats->restore_drop_sge = 0;
u64_stats_update_end(&rxq_stats->syncp);
}
@@ -155,8 +150,12 @@ static u32 hinic3_rx_fill_buffers(struct hinic3_rxq *rxq)
err = rx_alloc_mapped_page(rxq->page_pool, rx_info,
rxq->buf_len);
- if (unlikely(err))
+ if (unlikely(err)) {
+ u64_stats_update_begin(&rxq->rxq_stats.syncp);
+ rxq->rxq_stats.alloc_rx_buf_err++;
+ u64_stats_update_end(&rxq->rxq_stats.syncp);
break;
+ }
dma_addr = page_pool_get_dma_addr(rx_info->page) +
rx_info->page_offset;
@@ -170,6 +169,10 @@ static u32 hinic3_rx_fill_buffers(struct hinic3_rxq *rxq)
rxq->next_to_update << HINIC3_NORMAL_RQ_WQE);
rxq->delta -= i;
rxq->next_to_alloc = rxq->next_to_update;
+ } else if (free_wqebbs == rxq->q_depth - 1) {
+ u64_stats_update_begin(&rxq->rxq_stats.syncp);
+ rxq->rxq_stats.rx_buf_empty++;
+ u64_stats_update_end(&rxq->rxq_stats.syncp);
}
return i;
@@ -330,11 +333,24 @@ static void hinic3_rx_csum(struct hinic3_rxq *rxq, u32 offload_type,
struct net_device *netdev = rxq->netdev;
bool l2_tunnel;
+ /* HW ensures that only 1 bit is set in csum_err. */
+ if (unlikely(csum_err == HINIC3_RX_CSUM_IPSU_OTHER_ERR)) {
+ u64_stats_update_begin(&rxq->rxq_stats.syncp);
+ rxq->rxq_stats.other_errors++;
+ u64_stats_update_end(&rxq->rxq_stats.syncp);
+ }
+
if (!(netdev->features & NETIF_F_RXCSUM))
return;
if (unlikely(csum_err)) {
/* pkt type is recognized by HW, and csum is wrong */
+ if (csum_err != HINIC3_RX_CSUM_HW_CHECK_NONE &&
+ csum_err != HINIC3_RX_CSUM_IPSU_OTHER_ERR) {
+ u64_stats_update_begin(&rxq->rxq_stats.syncp);
+ rxq->rxq_stats.csum_errors++;
+ u64_stats_update_end(&rxq->rxq_stats.syncp);
+ }
skb->ip_summed = CHECKSUM_NONE;
return;
}
@@ -387,8 +403,12 @@ static int recv_one_pkt(struct hinic3_rxq *rxq, struct hinic3_rq_cqe *rx_cqe,
u16 num_lro;
skb = hinic3_fetch_rx_buffer(rxq, pkt_len);
- if (unlikely(!skb))
+ if (unlikely(!skb)) {
+ u64_stats_update_begin(&rxq->rxq_stats.syncp);
+ rxq->rxq_stats.alloc_skb_err++;
+ u64_stats_update_end(&rxq->rxq_stats.syncp);
return -ENOMEM;
+ }
/* place header in linear portion of buffer */
if (skb_is_nonlinear(skb))
@@ -550,11 +570,29 @@ int hinic3_configure_rxqs(struct net_device *netdev, u16 num_rq,
return 0;
}
+void hinic3_rxq_get_stats(struct hinic3_rxq *rxq,
+ struct hinic3_rxq_stats *stats)
+{
+ struct hinic3_rxq_stats *rxq_stats = &rxq->rxq_stats;
+ unsigned int start;
+
+ do {
+ start = u64_stats_fetch_begin(&rxq_stats->syncp);
+ stats->csum_errors = rxq_stats->csum_errors;
+ stats->other_errors = rxq_stats->other_errors;
+ stats->rx_buf_empty = rxq_stats->rx_buf_empty;
+ stats->alloc_skb_err = rxq_stats->alloc_skb_err;
+ stats->alloc_rx_buf_err = rxq_stats->alloc_rx_buf_err;
+ } while (u64_stats_fetch_retry(&rxq_stats->syncp, start));
+}
+
int hinic3_rx_poll(struct hinic3_rxq *rxq, int budget)
{
struct hinic3_nic_dev *nic_dev = netdev_priv(rxq->netdev);
+ u32 ip_type, offload_type, pkt_hdr_len;
u32 sw_ci, status, pkt_len, vlan_len;
struct hinic3_rq_cqe *rx_cqe;
+ u64 rx_bytes = 0;
u32 num_wqe = 0;
int nr_pkts = 0;
u16 num_lro;
@@ -574,10 +612,20 @@ int hinic3_rx_poll(struct hinic3_rxq *rxq, int budget)
if (recv_one_pkt(rxq, rx_cqe, pkt_len, vlan_len, status))
break;
+ rx_bytes += pkt_len;
nr_pkts++;
num_lro = RQ_CQE_STATUS_GET(status, NUM_LRO);
- if (num_lro)
+ if (num_lro) {
+ offload_type = le32_to_cpu(rx_cqe->offload_type);
+ ip_type = RQ_CQE_OFFOLAD_TYPE_GET(offload_type,
+ IP_TYPE);
+ pkt_hdr_len = ip_type == HINIC3_RX_IPV6_PKT ?
+ HINIC3_LRO_PKT_HDR_LEN_IPV6 :
+ HINIC3_LRO_PKT_HDR_LEN_IPV4;
+
+ rx_bytes += (num_lro - 1) * pkt_hdr_len;
num_wqe += hinic3_get_sge_num(rxq, pkt_len);
+ }
rx_cqe->status = 0;
@@ -588,5 +636,10 @@ int hinic3_rx_poll(struct hinic3_rxq *rxq, int budget)
if (rxq->delta >= HINIC3_RX_BUFFER_WRITE)
hinic3_rx_fill_buffers(rxq);
+ u64_stats_update_begin(&rxq->rxq_stats.syncp);
+ rxq->rxq_stats.packets += (u64)nr_pkts;
+ rxq->rxq_stats.bytes += rx_bytes;
+ u64_stats_update_end(&rxq->rxq_stats.syncp);
+
return nr_pkts;
}
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_rx.h b/drivers/net/ethernet/huawei/hinic3/hinic3_rx.h
index 06d1b3299e7c..c11d080408a7 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_rx.h
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_rx.h
@@ -8,6 +8,17 @@
#include <linux/dim.h>
#include <linux/netdevice.h>
+/* rx cqe checksum err */
+#define HINIC3_RX_CSUM_IP_CSUM_ERR BIT(0)
+#define HINIC3_RX_CSUM_TCP_CSUM_ERR BIT(1)
+#define HINIC3_RX_CSUM_UDP_CSUM_ERR BIT(2)
+#define HINIC3_RX_CSUM_IGMP_CSUM_ERR BIT(3)
+#define HINIC3_RX_CSUM_ICMPV4_CSUM_ERR BIT(4)
+#define HINIC3_RX_CSUM_ICMPV6_CSUM_ERR BIT(5)
+#define HINIC3_RX_CSUM_SCTP_CRC_ERR BIT(6)
+#define HINIC3_RX_CSUM_HW_CHECK_NONE BIT(7)
+#define HINIC3_RX_CSUM_IPSU_OTHER_ERR BIT(8)
+
#define RQ_CQE_OFFOLAD_TYPE_PKT_TYPE_MASK GENMASK(4, 0)
#define RQ_CQE_OFFOLAD_TYPE_IP_TYPE_MASK GENMASK(6, 5)
#define RQ_CQE_OFFOLAD_TYPE_TUNNEL_PKT_FORMAT_MASK GENMASK(11, 8)
@@ -39,7 +50,6 @@ struct hinic3_rxq_stats {
u64 rx_buf_empty;
u64 alloc_skb_err;
u64 alloc_rx_buf_err;
- u64 restore_drop_sge;
struct u64_stats_sync syncp;
};
@@ -123,6 +133,9 @@ void hinic3_free_rxqs_res(struct net_device *netdev, u16 num_rq,
u32 rq_depth, struct hinic3_dyna_rxq_res *rxqs_res);
int hinic3_configure_rxqs(struct net_device *netdev, u16 num_rq,
u32 rq_depth, struct hinic3_dyna_rxq_res *rxqs_res);
+
+void hinic3_rxq_get_stats(struct hinic3_rxq *rxq,
+ struct hinic3_rxq_stats *stats);
int hinic3_rx_poll(struct hinic3_rxq *rxq, int budget);
#endif
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_tx.c b/drivers/net/ethernet/huawei/hinic3/hinic3_tx.c
index 53c065194136..5b7075e9fe5d 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_tx.c
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_tx.c
@@ -97,8 +97,12 @@ static int hinic3_tx_map_skb(struct net_device *netdev, struct sk_buff *skb,
dma_info[0].dma = dma_map_single(&pdev->dev, skb->data,
skb_headlen(skb), DMA_TO_DEVICE);
- if (dma_mapping_error(&pdev->dev, dma_info[0].dma))
+ if (dma_mapping_error(&pdev->dev, dma_info[0].dma)) {
+ u64_stats_update_begin(&txq->txq_stats.syncp);
+ txq->txq_stats.map_frag_err++;
+ u64_stats_update_end(&txq->txq_stats.syncp);
return -EFAULT;
+ }
dma_info[0].len = skb_headlen(skb);
@@ -117,6 +121,9 @@ static int hinic3_tx_map_skb(struct net_device *netdev, struct sk_buff *skb,
skb_frag_size(frag),
DMA_TO_DEVICE);
if (dma_mapping_error(&pdev->dev, dma_info[idx].dma)) {
+ u64_stats_update_begin(&txq->txq_stats.syncp);
+ txq->txq_stats.map_frag_err++;
+ u64_stats_update_end(&txq->txq_stats.syncp);
err = -EFAULT;
goto err_unmap_page;
}
@@ -260,6 +267,9 @@ static int hinic3_tx_csum(struct hinic3_txq *txq, struct hinic3_sq_task *task,
if (l4_proto != IPPROTO_UDP ||
((struct udphdr *)skb_transport_header(skb))->dest !=
VXLAN_OFFLOAD_PORT_LE) {
+ u64_stats_update_begin(&txq->txq_stats.syncp);
+ txq->txq_stats.unknown_tunnel_pkt++;
+ u64_stats_update_end(&txq->txq_stats.syncp);
/* Unsupported tunnel packet, disable csum offload */
return skb_checksum_help(skb);
}
@@ -436,6 +446,27 @@ static u32 hinic3_tx_offload(struct sk_buff *skb, struct hinic3_sq_task *task,
return offload;
}
+static void hinic3_get_pkt_stats(struct hinic3_txq *txq, struct sk_buff *skb)
+{
+ u32 hdr_len, tx_bytes;
+ unsigned short pkts;
+
+ if (skb_is_gso(skb)) {
+ hdr_len = (skb_shinfo(skb)->gso_segs - 1) *
+ skb_tcp_all_headers(skb);
+ tx_bytes = skb->len + hdr_len;
+ pkts = skb_shinfo(skb)->gso_segs;
+ } else {
+ tx_bytes = skb->len > ETH_ZLEN ? skb->len : ETH_ZLEN;
+ pkts = 1;
+ }
+
+ u64_stats_update_begin(&txq->txq_stats.syncp);
+ txq->txq_stats.bytes += tx_bytes;
+ txq->txq_stats.packets += pkts;
+ u64_stats_update_end(&txq->txq_stats.syncp);
+}
+
static u16 hinic3_get_and_update_sq_owner(struct hinic3_io_queue *sq,
u16 curr_pi, u16 wqebb_cnt)
{
@@ -542,8 +573,12 @@ static netdev_tx_t hinic3_send_one_skb(struct sk_buff *skb,
int err;
if (unlikely(skb->len < MIN_SKB_LEN)) {
- if (skb_pad(skb, MIN_SKB_LEN - skb->len))
+ if (skb_pad(skb, MIN_SKB_LEN - skb->len)) {
+ u64_stats_update_begin(&txq->txq_stats.syncp);
+ txq->txq_stats.skb_pad_err++;
+ u64_stats_update_end(&txq->txq_stats.syncp);
goto err_out;
+ }
skb->len = MIN_SKB_LEN;
}
@@ -598,6 +633,7 @@ static netdev_tx_t hinic3_send_one_skb(struct sk_buff *skb,
txq->tx_stop_thrs,
txq->tx_start_thrs);
+ hinic3_get_pkt_stats(txq, skb);
hinic3_prepare_sq_ctrl(&wqe_combo, queue_info, num_sge, owner);
hinic3_write_db(txq->sq, 0, DB_CFLAG_DP_SQ,
hinic3_get_sq_local_pi(txq->sq));
@@ -607,6 +643,10 @@ static netdev_tx_t hinic3_send_one_skb(struct sk_buff *skb,
err_drop_pkt:
dev_kfree_skb_any(skb);
err_out:
+ u64_stats_update_begin(&txq->txq_stats.syncp);
+ txq->txq_stats.dropped++;
+ u64_stats_update_end(&txq->txq_stats.syncp);
+
return NETDEV_TX_OK;
}
@@ -757,6 +797,24 @@ int hinic3_configure_txqs(struct net_device *netdev, u16 num_sq,
return 0;
}
+void hinic3_txq_get_stats(struct hinic3_txq *txq,
+ struct hinic3_txq_stats *stats)
+{
+ struct hinic3_txq_stats *txq_stats = &txq->txq_stats;
+ unsigned int start;
+
+ do {
+ start = u64_stats_fetch_begin(&txq_stats->syncp);
+ stats->busy = txq_stats->busy;
+ stats->skb_pad_err = txq_stats->skb_pad_err;
+ stats->frag_len_overflow = txq_stats->frag_len_overflow;
+ stats->offload_cow_skb_err = txq_stats->offload_cow_skb_err;
+ stats->map_frag_err = txq_stats->map_frag_err;
+ stats->unknown_tunnel_pkt = txq_stats->unknown_tunnel_pkt;
+ stats->frag_size_err = txq_stats->frag_size_err;
+ } while (u64_stats_fetch_retry(&txq_stats->syncp, start));
+}
+
bool hinic3_tx_poll(struct hinic3_txq *txq, int budget)
{
struct net_device *netdev = txq->netdev;
diff --git a/drivers/net/ethernet/huawei/hinic3/hinic3_tx.h b/drivers/net/ethernet/huawei/hinic3/hinic3_tx.h
index 00194f2a1bcc..0a21c423618f 100644
--- a/drivers/net/ethernet/huawei/hinic3/hinic3_tx.h
+++ b/drivers/net/ethernet/huawei/hinic3/hinic3_tx.h
@@ -157,6 +157,8 @@ int hinic3_configure_txqs(struct net_device *netdev, u16 num_sq,
u32 sq_depth, struct hinic3_dyna_txq_res *txqs_res);
netdev_tx_t hinic3_xmit_frame(struct sk_buff *skb, struct net_device *netdev);
+void hinic3_txq_get_stats(struct hinic3_txq *txq,
+ struct hinic3_txq_stats *stats);
bool hinic3_tx_poll(struct hinic3_txq *txq, int budget);
void hinic3_flush_txqs(struct net_device *netdev);
--
2.54.0
^ permalink raw reply related
* [PATCH net-next v11 0/5] net: hinic3: PF initialization
From: Fan Gong @ 2026-07-20 12:53 UTC (permalink / raw)
To: Fan Gong, Wu Di, Teng Peisen, netdev, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Andrew Lunn, Ioana Ciornei, Mohsin Bashir, Dimitri Daskalakis,
Harshitha Ramamurthy
Cc: linux-kernel, linux-doc, luosifu, Xin Guo, Zhou Shuai, Wu Like,
Shi Jing, Zheng Jiezhen, Maxime Chevallier
From: Super User <root@localhost.localdomain>
This is [3/3] part of hinic3 Ethernet driver second submission.
With this patch hinic3 becomes a complete Ethernet driver with
pf and vf.
Add 20 ethtool ops for information of queue, rss, coalesce and eth data.
Add MTU size validation
Config netdev watchdog timeout.
Remove unneed coalesce parameters.
Changes:
PATCH 03 V01: https://lore.kernel.org/netdev/cover.1773387649.git.zhuyikai1@h-partners.com/
* Add rmon/pause/phy/mac/ctrl stats (Ioana Ciornei)
PATCH 03 V02: https://lore.kernel.org/netdev/cover.1774684571.git.zhuyikai1@h-partners.com/
* Modify "return -EINVAL" intension problem (AI review)
* Use le16_to_cpu for rss_indir pair.out->buf (AI review)
* Use u32 instead of int in coalesce_limits to avoid overflow (AI review)
* Remove redundant u64_stats_update_begin/end when reading stats without
concurrent reader (AI review)
* Modify nic_dev->stats.syncp logic (AI review)
* Complete rxq/txq stats stats fileds in hinic3_rx/txq_get_stats (AI review)
* Remove statistics values in rtnl_link_stats64 from ethtool statistics
values (AI review)
* Add channel_cfg_lock & channel_res_lock to protect resources access (AI review)
* Remove OutOfRangeLengthField, FrameToolong and InRangeLengthErrors (Ioana Ciornei)
* Remove redundant mtu commit (Maxime Chevialler)
PATCH 03 V03: https://lore.kernel.org/netdev/cover.1774940117.git.zhuyikai1@h-partners.com/
* Change unnedd to unneeded (AI review)
* Remove packets,bytes,errors and dropped in hinic3_rx/tx_queue_stats (AI review)
* Remove duplicated entried in hinic3_port_stats[] (AI review)
* change stats_info.head.status to ps->head.status (AI review)
PATCH 03 V04: https://lore.kernel.org/netdev/cover.1775618797.git.zhuyikai1@h-partners.com/
* Remove restore_drop_sge in hinic3_rx_queue_stats (AI review)
* Remove hinic3_nic_stats (AI review)
* Use old_q_param to store old config and use it in error handling (Mohsin Bashir)
* Add netdev_info to inform the user that depth is trimmed (Mohsin Bashir)
* Remove const in hinic3_get_qp_stats_strings parameters (Mohsin Bashir)
* Change EOPNOTSUPP to ERANGE in is_coalesce_exceed_limit (Mohsin Bashir)
* Update nic_dev->rss_type after hinic3_set_rss_type (Mohsin Bashir)
* Modify MGMT_STATUS_CMD_UNSUPPORTED to EOPNOTSUPP for complying with the
error code specifications (Mohsin Bashir)
PATCH 03 V05: https://lore.kernel.org/netdev/cover.1775711066.git.zhuyikai1@h-partners.com/
* Clear HINIC3_CHANGE_RES_INVALID bit in error handling (AI review)
* Use low >= high to avoid low=high in is_coalesce_legal (AI review)
* As tx and rx share interrupts, we only use ETHTOOL_COALESCE_RX_USECS for
user setting to avoid user misunderstanding. So we do not add
ETHTOOL_COALESCE_TX_USECS. (Mohsin Bashir & AI review)
PATCH 03 V06: https://lore.kernel.org/netdev/cover.1779867397.git.zhuyikai1@h-partners.com/
* Remove redundant rx_jumbo_pending and rx_mini_pending judgement (Jakub Kicinski)
* Remove redundant max tx_pending judgement when .get_ringparam already got the
max value (Jakub Kicinski)
* Use extack instead of netdev_err/netdev_info/netdev_warning (Jakub Kicinski)
* Remove HINIC3_CHANNEL_RES_VALID and only use HINIC3_CHANGE_RES_INVALID
bit (Jakub Kicinski)
* Deference freed pointers in hinic3_change_channel_settings error
handling (Jakub Kicinski)
* Modify hinic3_open_channel (Jakub Kicinski)
PATCH 03 V07: https://lore.kernel.org/netdev/cover.1779940072.git.zhuyikai1@h-partners.com/
* Remove the trailing '\n' in NL_SET_ERR_MSG* (Jakub Kicinski)
PATCH 03 V08: https://lore.kernel.org/netdev/cover.1780907605.git.wudi234@huawei.com/
* Update patch commit to be more accurate for change information (AI review)
* Modify <net/devlink.h> to <linux/netlink.h> (AI review)
* Use sq/rq_depth instead of rx/txqs[0].q_depth (AI review)
* hinic3_change_channel_settings() runs under change_res_mutex
to protect dynamic channel updates (AI review)
* hinic3_close() serializes close and channel reconfiguration paths (AI review)
* Remove useless u64_stats_init in hinic3_get_drv_queue_stats() (AI review)
* hinic3_get_drv_queue_stats() fills rxq stats at correct idx
when txq is null (AI review)
* Remove unnecessary semicolons after the closing bracket of
static inline functions (AI review)
* Disable preemption in hinic3_rx_fill_buffers() (AI review)
* Add spinlock to protect per-queue coalesce parameters from
concurrent access (AI review)
* Reject static coalesce config when adaptive RX coalesce is
enabled (AI review)
* Validate interface state and queue ID before modifying coalesce
parameters (AI review)
* Differentiate get_coalesce output for adaptive/static modes and
zero-fill the struct (AI review)
* Return -ERANGE for invalid coalesce frame limits and simplify
error message (AI review)
* Remove unused watchdog timeout definition and assignment (AI review)
* Return -EOPNOTSUPP when RSS type query is unsupported by firmware (AI review)
* Reject L4 hash bits for pure L3 flow types (AI review)
* Split RSS type conversion to correctly handle L3-only flows (AI review)
* Reprogram RSS indir table and HW parameters after channel count
changes (AI review)
* Propagate exact error code from channel parameter validation (AI review)
* Update SW RSS indir copy only after HW programming succeeds (AI review)
* Program RSS hash type to HW before updating SW state to prevent
inconsistency (AI review)
PATCH 03 V09: https://lore.kernel.org/netdev/cover.1781062575.git.wudi234@huawei.com/
* Remove useless messages in hinic3_set_ringparam() (Jakub Kicinski)
* Solve the big-endian and little-endian issue (Jakub Kicinski)
PATCH 03 V10: https://lore.kernel.org/netdev/cover.1782718232.git.wudi234@huawei.com/
* Unify 3 mutex in struct hinic3_nic_dev: port_state_mutex, change_res_mutex,
coal_lock.
* Correct copy-paste error in hinic3_get_ringparam() (AI Review and
Harshitha Ramamurthy)
* hinic3_set_interrupt_moder() now compare q_id and num_qps inside mutex lock
(AI Review)
* Error branches in hinic3_link_status_change() goto unlock mutex before return
(AI Review)
* In hinic3_change_channel_settings(), hinic3_config_num_qps() and
hinic3_alloc_channel_resources() now run inside mutex lock. (AI Review)
* Call hinic3_free_qp_ctxts() in error paths of hinic3_open_channel(),
thus hinic3_change_channel_settings() don't call it in error path (AI Review)
* Unset HINIC3_INTF_UP during close and reopen in hinic3_change_channel_settings
* hinic3_change_channel_settings() restore previous resources before freeing
new_qp_params (AI Review)
* Delete unused macro HINIC3_LRO_PKT_HDR_LEN (AI Review)
* Drop packets when skb_checksum_help() return error (AI Review)
* Use rxq->q_mask instead of rxq->q_depth - 1 in hinic3_configure_rxqs()
(Dimitri Daskalakis)
* Clarify extack error message in hinic3_check_ringparam_valid()
(Harshitha Ramamurthy)
* Use rounddown_pow_of_two() instead of 1U << ilog2() in hinic3_set_ringparam()
for readability. (Harshitha Ramamurthy)
* Protect interrupt coalesce parameter reads with state_lock to prevent data
races(AI Review)
* Restore previous coalesce parameters on HW configuration failure (AI Review)
* Always report all coalesce parameters regardless of adaptive mode (AI Review)
* Safely stop DIM work when disabling adaptive RX coalescing (AI Review)
* Change coalesce disabled message from extack error to info log (AI Review)
* Implement dedicated get_rx_ring_count ethtool operation (Dimitri Daskalakis)
* Pass new RSS hash type to HW instead of the old one (AI Review)
* Initialize default RSS indirection table before HW programming when not
user-configured(AI Review)
* Revert queue count on RSS reprogramming failure (AI Review)
* Use sizeof for RSS indirection table copy to prevent size mismatch (AI Review)
PATCH 03 V11:
* Remove state_lock in hinic3_set_interrupt_moder() and
hinic3_link_status_change() (Andrew Lunn and AI Review)
* In hinic3_change_channel_settings(), cur_trxq_params is read after taking lock,
and get symmetrically restored when alloc resources failed. (AI Review)
* Remove preempt_disable()/preempt_enable() pairs in hinic3_rx_fill_buffers().
(AI Review)
* Add a comment about csum_err, and rewrite its judgement in hinic3_rx_csum().
(AI Review)
* Rollback hash function type to previous state if setting RSS indirection
table or key fails (AI Review)
* Allow users to disable L3 hashing for IPV4/IPV6 flows by properly handling
empty hash tuple in ethtool (AI Review)
* Update software queue coalesce state only after successful hardware
configuration to avoid manual rollback (AI Review)
* Use READ_ONCE and WRITE_ONCE for adaptive_rx_coal to ensure safe
concurrent memory access (AI Review)
* Reset RX queue DIM state to DIM_START_MEASURE when canceling
adaptive RX coalescing work (AI Review)
* Remove unnecessary state_lock mutexes when reading queue coalesce
parameters in IRQ and update paths (AI Review)
* Rename is_coalesce_legal to hinic3_validate_coalesce for better code
readability and naming consistency (AI Review)
Fan Gong (5):
hinic3: Add ethtool queue ops
hinic3: Add ethtool statistic ops
hinic3: Add ethtool coalesce ops
hinic3: Add ethtool rss ops
hinic3: Remove unneeded coalesce parameters
.../ethernet/huawei/hinic3/hinic3_ethtool.c | 870 +++++++++++++++++-
.../ethernet/huawei/hinic3/hinic3_hw_intf.h | 11 +
.../net/ethernet/huawei/hinic3/hinic3_irq.c | 18 +-
.../net/ethernet/huawei/hinic3/hinic3_main.c | 6 +-
.../huawei/hinic3/hinic3_mgmt_interface.h | 39 +
.../huawei/hinic3/hinic3_netdev_ops.c | 106 ++-
.../ethernet/huawei/hinic3/hinic3_nic_cfg.c | 64 ++
.../ethernet/huawei/hinic3/hinic3_nic_cfg.h | 109 +++
.../ethernet/huawei/hinic3/hinic3_nic_dev.h | 11 +-
.../ethernet/huawei/hinic3/hinic3_nic_io.c | 4 +-
.../ethernet/huawei/hinic3/hinic3_nic_io.h | 8 +-
.../net/ethernet/huawei/hinic3/hinic3_rss.c | 561 ++++++++++-
.../net/ethernet/huawei/hinic3/hinic3_rss.h | 20 +
.../net/ethernet/huawei/hinic3/hinic3_rx.c | 71 +-
.../net/ethernet/huawei/hinic3/hinic3_rx.h | 18 +-
.../net/ethernet/huawei/hinic3/hinic3_tx.c | 69 +-
.../net/ethernet/huawei/hinic3/hinic3_tx.h | 2 +
17 files changed, 1942 insertions(+), 45 deletions(-)
base-commit: ce6b4d3216b63f902bb8e9695ee6c10c83415f65
--
2.54.0
^ permalink raw reply
* Re: [Intel-wired-lan] [PATCH iwl-next] idpf: add flow-based XDP fallback for FWs without Tx FIFO support
From: Alexander Lobakin @ 2026-07-20 12:48 UTC (permalink / raw)
To: intel-wired-lan
Cc: Tony Nguyen, Przemek Kitszel, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
nxne.cnse.osdt.itp.upstreaming, netdev, linux-kernel
In-Reply-To: <20260708151327.1091570-1-aleksander.lobakin@intel.com>
From: Alexander Lobakin <aleksander.lobakin@intel.com>
Date: Wed, 8 Jul 2026 17:13:27 +0200
> From the first days of XDP implementation in idpf, it relied and
> worked solely on top of the queue-based scheduling Tx mode, which
> basically means simple FIFO. However, turned out not every firmware
> supports this mode and XDP doesn't work there at all.
Ok I'm back from the conferences.
I need to take a look at Sashiko's feedback, some pieces seem to make sense.
Thanks,
Olek
^ permalink raw reply
* Re: [PATCH] net: ipv6: fix dif and sdif mismatch in raw6_icmp_error
From: Joe Damato @ 2026-07-20 12:48 UTC (permalink / raw)
To: lirongqing
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Kuniyuki Iwashima, Willem de Bruijn, Kees Cook,
netdev, linux-kernel
In-Reply-To: <20260717143230.1836-1-lirongqing@baidu.com>
On Fri, Jul 17, 2026 at 10:32:30PM +0800, lirongqing wrote:
> From: Li RongQing <lirongqing@baidu.com>
>
> In raw6_icmp_error(), raw_v6_match() is called with inet6_iif(skb) passed
> to both the 'dif' and 'sdif' arguments. This is a copy-paste or typo error,
> as the last argument should represent the secondary interface index (sdif).
>
> This mismatch breaks ICMPv6 error handling for IPv6 raw sockets in VRF
> (Virtual Routing and Forwarding) environments. When a raw socket is bound
> to a VRF master device, raw_v6_match() fails to find a match because it is
> not given the correct sdif value, causing the socket to miss relevant
> ICMPv6 error notifications.
>
> Fix this by properly passing inet6_sdif(skb) as the last argument to
> raw_v6_match().
>
> Fixes: 5108ab4bf446fa ("net: ipv6: add second dif to raw socket lookups")
> Signed-off-by: Li RongQing <lirongqing@baidu.com>
> ---
> net/ipv6/raw.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/net/ipv6/raw.c b/net/ipv6/raw.c
> index 3cc5869..b88d364 100644
> --- a/net/ipv6/raw.c
> +++ b/net/ipv6/raw.c
> @@ -349,7 +349,7 @@ void raw6_icmp_error(struct sk_buff *skb, int nexthdr,
> const struct ipv6hdr *ip6h = (const struct ipv6hdr *)skb->data;
>
> if (!raw_v6_match(net, sk, nexthdr, &ip6h->saddr, &ip6h->daddr,
> - inet6_iif(skb), inet6_iif(skb)))
> + inet6_iif(skb), inet6_sdif(skb)))
Looking at the commit under fixes, this does look like a copy/paste bug to me.
I wonder if a future contribution would be a test to exercise this path?
Reviewed-by: Joe Damato <joe@dama.to>
^ permalink raw reply
* Re: [net-next,v4] selftests/net/openvswitch: add SCTP flow key test
From: Aaron Conole @ 2026-07-20 12:42 UTC (permalink / raw)
To: houminxi; +Cc: netdev, davem, edumazet, kuba, pabeni, i.maximets, echaudro
In-Reply-To: <20260719162657.3263089-1-houminxi@gmail.com>
Minxi Hou <houminxi@gmail.com> writes:
> Add test_sctp_connect_v4() to verify OVS can match on SCTP flow keys
> (sctp src/dst port).
>
> The test sets up client and server namespaces connected through an
> OVS bridge, installs port-keyed flows, and verifies:
> - sctp(dst=4443) matches client-to-server INIT
> - sctp(src=4443) matches server-to-client INIT-ACK
> - removing flows drops the connection
> - reinstalling flows restores connectivity
>
> Signed-off-by: Minxi Hou <houminxi@gmail.com>
> ---
> .../selftests/net/openvswitch/openvswitch.sh | 105 ++++++++++++++++++
> .../selftests/net/openvswitch/ovs-dpctl.py | 5 +
> 2 files changed, 110 insertions(+)
>
> v3 -> v4: rebase onto latest net-next (2026-07-19), resolve test list
> conflict from merged trunc test
Hi Minxi,
Just a note that this will conflict with the current in-flight patch::
[+] 1. #14675655 [new ] [net-next,v5]
selftests/net/openvswitch: add ICMPv6 echo type match test
It would be best to wait before reposting, or to bundle all of your test
case updates in a single series.
Given the other patch is already reviewed by me, let's wait until that
is accepted upstream before reposting this. I know Ilya had some
comments about it previously, but haven't checked to see if you've
addressed all of them. And a nit:
[...]
> +# sctp_connect_v4 test
> +# - sctp(dst=4443) matches client-to-server INIT
> +# - sctp(src=4443) matches server-to-client INIT-ACK
> +# - remove flows and verify connection fails, reinstall and recover
> +test_sctp_connect_v4() {
> + local t="test_sctp_connect_v4"
> +
> + which nc >/dev/null 2>&1 || return $ksft_skip
> + nc --sctp -z 127.0.0.1 1 </dev/null 2>/dev/null || return $ksft_skip
This can probably be instead::
nc --help 2>&1 | grep -q -- --sctp || return $ksft_skip
This reads nicer to detect SCTP support in 'nc' binary.
^ permalink raw reply
* [PATCH net-next v3 3/3] selftests/net: Test PACKET_AUXDATA
From: Joe Damato @ 2026-07-20 12:27 UTC (permalink / raw)
To: netdev, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Shuah Khan
Cc: andrew+netdev, willemb, linux-kselftest, Joe Damato, linux-kernel
In-Reply-To: <20260720122714.759175-1-joe@dama.to>
Extend the packet socket selftest, adding a recvmsg path, to test
PACKET_AUXDATA. Check basic attributes of tpacket_auxdata.
Signed-off-by: Joe Damato <joe@dama.to>
---
tools/testing/selftests/net/psock_snd.c | 70 ++++++++++++++++++++++--
tools/testing/selftests/net/psock_snd.sh | 5 ++
2 files changed, 70 insertions(+), 5 deletions(-)
v3:
- Prevent combining test_auxdata and test_drops.
- Include an aligned(8) for the cmsg to ensure (1) buffer
alignment for cmsg struct fields and (2) aligned payload access.
v2:
- Add is_psock bool argument to do_rx.
- Factor out aux data check into its own function for readability.
diff --git a/tools/testing/selftests/net/psock_snd.c b/tools/testing/selftests/net/psock_snd.c
index 81096df5cffc..bee81227f5b0 100644
--- a/tools/testing/selftests/net/psock_snd.c
+++ b/tools/testing/selftests/net/psock_snd.c
@@ -40,6 +40,7 @@ static bool cfg_use_qdisc_bypass;
static bool cfg_use_vlan;
static bool cfg_use_vnet;
static bool cfg_drop;
+static bool cfg_aux_data;
static char *cfg_ifname = "lo";
static int cfg_mtu = 1500;
@@ -279,11 +280,54 @@ static int setup_rx(void)
return fd;
}
-static void do_rx(int fd, int expected_len, char *expected)
+static void check_aux_data(struct cmsghdr *cmsg, int expected_len)
{
+ struct tpacket_auxdata *adata;
+
+ if (!cmsg)
+ error(1, 0, "auxdata null");
+
+ if (cmsg->cmsg_level != SOL_PACKET)
+ error(1, 0, "cmsg_level != SOL_PACKET");
+
+ if (cmsg->cmsg_type != PACKET_AUXDATA)
+ error(1, 0, "cmsg_type != PACKET_AUXDATA");
+
+ adata = (struct tpacket_auxdata *)CMSG_DATA(cmsg);
+
+ if (adata->tp_net != ETH_HLEN)
+ error(1, 0, "cmsg tp_net != ETH_HLEN");
+
+ if (adata->tp_len != expected_len)
+ error(1, 0, "cmsg tp_len != %u", expected_len);
+
+ if (adata->tp_snaplen != expected_len)
+ error(1, 0, "cmsg tp_snaplen != %u", expected_len);
+}
+
+static void do_rx(int fd, int expected_len, char *expected, bool is_psock)
+{
+ char cmsg_buf[1024] __attribute__((aligned(8))) = {};
+ bool aux = is_psock && cfg_aux_data;
+ struct msghdr msg = {};
+ struct iovec iov[1];
int ret;
- ret = recv(fd, rbuf, sizeof(rbuf), 0);
+ if (aux) {
+ iov[0].iov_base = rbuf;
+ iov[0].iov_len = sizeof(rbuf);
+
+ msg.msg_iov = iov;
+ msg.msg_iovlen = 1;
+
+ msg.msg_control = cmsg_buf;
+ msg.msg_controllen = sizeof(cmsg_buf);
+
+ ret = recvmsg(fd, &msg, 0);
+ } else {
+ ret = recv(fd, rbuf, sizeof(rbuf), 0);
+ }
+
if (ret == -1)
error(1, errno, "recv");
if (ret != expected_len)
@@ -292,6 +336,12 @@ static void do_rx(int fd, int expected_len, char *expected)
if (memcmp(rbuf, expected, ret))
error(1, 0, "recv: data mismatch");
+ if (aux) {
+ struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
+
+ check_aux_data(cmsg, expected_len);
+ }
+
fprintf(stderr, "rx: %u\n", ret);
}
@@ -312,6 +362,10 @@ static int setup_sniffer(void)
if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &one, sizeof(one)))
error(1, errno, "setsockopt SO_RCVBUF");
+ if (cfg_aux_data)
+ if (setsockopt(fd, SOL_PACKET, PACKET_AUXDATA, &one, sizeof(one)))
+ error(1, errno, "setsockopt PACKET_AUXDATA");
+
pair_udp_setfilter(fd);
do_bind(fd);
@@ -322,8 +376,11 @@ static void parse_opts(int argc, char **argv)
{
int c;
- while ((c = getopt(argc, argv, "bcCdDgl:qt:vV")) != -1) {
+ while ((c = getopt(argc, argv, "abcCdDgl:qt:vV")) != -1) {
switch (c) {
+ case 'a':
+ cfg_aux_data = true;
+ break;
case 'b':
cfg_use_bind = true;
break;
@@ -373,6 +430,9 @@ static void parse_opts(int argc, char **argv)
if (cfg_use_gso && !cfg_use_csum_off)
error(1, 0, "option gso (-g) requires csum offload (-c)");
+
+ if (cfg_aux_data && cfg_drop)
+ error(1, 0, "option aux data (-a) conflicts with drop (-D)");
}
static void check_packet_stats(int fd)
@@ -432,11 +492,11 @@ static void run_test(void)
/* BPF filter accepts only this length, vlan changes MAC */
if (cfg_payload_len == DATA_LEN && !cfg_use_vlan) {
do_rx(fds, total_len - sizeof(struct virtio_net_hdr),
- tbuf + sizeof(struct virtio_net_hdr));
+ tbuf + sizeof(struct virtio_net_hdr), true);
check_packet_stats(fds);
}
- do_rx(fdr, cfg_payload_len, tbuf + total_len - cfg_payload_len);
+ do_rx(fdr, cfg_payload_len, tbuf + total_len - cfg_payload_len, false);
out:
if (close(fds))
diff --git a/tools/testing/selftests/net/psock_snd.sh b/tools/testing/selftests/net/psock_snd.sh
index b6ef12fad5d5..111c9e2f0d21 100755
--- a/tools/testing/selftests/net/psock_snd.sh
+++ b/tools/testing/selftests/net/psock_snd.sh
@@ -97,4 +97,9 @@ echo "raw gso max size + 1 (expected to fail)"
echo "test drops statistics"
./in_netns.sh ./psock_snd -D
+# test aux data
+
+echo "test aux data"
+./in_netns.sh ./psock_snd -a
+
echo "OK. All tests passed"
--
2.53.0-Meta
^ permalink raw reply related
* [PATCH net-next v3 2/3] selftests/net: Test PACKET_STATISTICS drops
From: Joe Damato @ 2026-07-20 12:27 UTC (permalink / raw)
To: netdev, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Shuah Khan
Cc: andrew+netdev, willemb, linux-kselftest, Joe Damato, linux-kernel
In-Reply-To: <20260720122714.759175-1-joe@dama.to>
Extend psock_snd to test drops by setting a tiny receive buffer and
sending a large burst of packets.
Signed-off-by: Joe Damato <joe@dama.to>
---
tools/testing/selftests/net/psock_snd.c | 48 ++++++++++++++++++++----
tools/testing/selftests/net/psock_snd.sh | 5 +++
2 files changed, 46 insertions(+), 7 deletions(-)
v2:
- Remove do_tx argument and use global cfg_drop instead
diff --git a/tools/testing/selftests/net/psock_snd.c b/tools/testing/selftests/net/psock_snd.c
index 5be481a3d2bd..81096df5cffc 100644
--- a/tools/testing/selftests/net/psock_snd.c
+++ b/tools/testing/selftests/net/psock_snd.c
@@ -39,6 +39,7 @@ static bool cfg_use_gso;
static bool cfg_use_qdisc_bypass;
static bool cfg_use_vlan;
static bool cfg_use_vnet;
+static bool cfg_drop;
static char *cfg_ifname = "lo";
static int cfg_mtu = 1500;
@@ -49,6 +50,8 @@ static uint16_t cfg_port = 8000;
/* test sending up to max mtu + 1 */
#define TEST_SZ (sizeof(struct virtio_net_hdr) + ETH_HLEN + ETH_MAX_MTU + 1)
+#define BURST_CNT (1000)
+
static char tbuf[TEST_SZ], rbuf[TEST_SZ];
static unsigned long add_csum_hword(const uint16_t *start, int num_u16)
@@ -212,13 +215,14 @@ static void do_send(int fd, char *buf, int len)
if (ret != len)
error(1, 0, "write: %u %u", ret, len);
- fprintf(stderr, "tx: %u\n", ret);
+ if (!cfg_drop)
+ fprintf(stderr, "tx: %u\n", ret);
}
static int do_tx(void)
{
const int one = 1;
- int fd, len;
+ int i, fd, len;
fd = socket(PF_PACKET, cfg_use_dgram ? SOCK_DGRAM : SOCK_RAW, 0);
if (fd == -1)
@@ -242,6 +246,10 @@ static int do_tx(void)
do_send(fd, tbuf, len);
+ if (cfg_drop)
+ for (i = 0; i < BURST_CNT; i++)
+ do_send(fd, tbuf, len);
+
if (close(fd))
error(1, errno, "close t");
@@ -290,6 +298,7 @@ static void do_rx(int fd, int expected_len, char *expected)
static int setup_sniffer(void)
{
struct timeval tv = { .tv_usec = 100 * 1000 };
+ const int one = 1;
int fd;
fd = socket(PF_PACKET, SOCK_RAW, 0);
@@ -299,6 +308,10 @@ static int setup_sniffer(void)
if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)))
error(1, errno, "setsockopt rcv timeout");
+ if (cfg_drop)
+ if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &one, sizeof(one)))
+ error(1, errno, "setsockopt SO_RCVBUF");
+
pair_udp_setfilter(fd);
do_bind(fd);
@@ -309,7 +322,7 @@ static void parse_opts(int argc, char **argv)
{
int c;
- while ((c = getopt(argc, argv, "bcCdgl:qt:vV")) != -1) {
+ while ((c = getopt(argc, argv, "bcCdDgl:qt:vV")) != -1) {
switch (c) {
case 'b':
cfg_use_bind = true;
@@ -323,6 +336,9 @@ static void parse_opts(int argc, char **argv)
case 'd':
cfg_use_dgram = true;
break;
+ case 'D':
+ cfg_drop = true;
+ break;
case 'g':
cfg_use_gso = true;
break;
@@ -367,11 +383,23 @@ static void check_packet_stats(int fd)
if (getsockopt(fd, SOL_PACKET, PACKET_STATISTICS, &st, &len))
error(1, errno, "getsockopt packet statistics");
- if (st.tp_packets != 1)
- error(1, 0, "stats: tp_packets %u != 1", st.tp_packets);
+ if (cfg_drop) {
+ /* PACKET_STATISTICS reports all packets seen (including
+ * drops) in tp_packets
+ */
+ if (st.tp_packets < st.tp_drops)
+ error(1, 0, "stats: tp_packets %u < tp_drops %u",
+ st.tp_packets, st.tp_drops);
- if (st.tp_drops != 0)
- error(1, 0, "stats: tp_drops %u != 0", st.tp_drops);
+ if (st.tp_drops == 0)
+ error(1, 0, "stats: expected drops but tp_drops == 0");
+ } else {
+ if (st.tp_packets != 1)
+ error(1, 0, "stats: tp_packets %u != 1", st.tp_packets);
+
+ if (st.tp_drops != 0)
+ error(1, 0, "stats: tp_drops %u != 0", st.tp_drops);
+ }
/* verify clear on read */
memset(&st, 0xff, sizeof(st));
@@ -396,6 +424,11 @@ static void run_test(void)
total_len = do_tx();
+ if (cfg_drop) {
+ check_packet_stats(fds);
+ goto out;
+ }
+
/* BPF filter accepts only this length, vlan changes MAC */
if (cfg_payload_len == DATA_LEN && !cfg_use_vlan) {
do_rx(fds, total_len - sizeof(struct virtio_net_hdr),
@@ -405,6 +438,7 @@ static void run_test(void)
do_rx(fdr, cfg_payload_len, tbuf + total_len - cfg_payload_len);
+out:
if (close(fds))
error(1, errno, "close s");
if (close(fdr))
diff --git a/tools/testing/selftests/net/psock_snd.sh b/tools/testing/selftests/net/psock_snd.sh
index 1cbfeb5052ec..b6ef12fad5d5 100755
--- a/tools/testing/selftests/net/psock_snd.sh
+++ b/tools/testing/selftests/net/psock_snd.sh
@@ -92,4 +92,9 @@ echo "raw gso max size"
echo "raw gso max size + 1 (expected to fail)"
(! ./in_netns.sh ./psock_snd -v -c -g -l "${max_mss_exceeds}")
+# test drops statistics
+
+echo "test drops statistics"
+./in_netns.sh ./psock_snd -D
+
echo "OK. All tests passed"
--
2.53.0-Meta
^ permalink raw reply related
* [PATCH net-next v3 1/3] selftests/net: Test PACKET_STATISTICS
From: Joe Damato @ 2026-07-20 12:27 UTC (permalink / raw)
To: netdev, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Shuah Khan
Cc: andrew+netdev, willemb, linux-kselftest, Joe Damato, linux-kernel
In-Reply-To: <20260720122714.759175-1-joe@dama.to>
Update the existing packet socket test to include a test for the sockopt
PACKET_STATISTICS.
Signed-off-by: Joe Damato <joe@dama.to>
---
tools/testing/selftests/net/psock_snd.c | 32 ++++++++++++++++++++++++-
1 file changed, 31 insertions(+), 1 deletion(-)
diff --git a/tools/testing/selftests/net/psock_snd.c b/tools/testing/selftests/net/psock_snd.c
index edf1e6f80d41..5be481a3d2bd 100644
--- a/tools/testing/selftests/net/psock_snd.c
+++ b/tools/testing/selftests/net/psock_snd.c
@@ -359,6 +359,34 @@ static void parse_opts(int argc, char **argv)
error(1, 0, "option gso (-g) requires csum offload (-c)");
}
+static void check_packet_stats(int fd)
+{
+ struct tpacket_stats st = {};
+ socklen_t len = sizeof(st);
+
+ if (getsockopt(fd, SOL_PACKET, PACKET_STATISTICS, &st, &len))
+ error(1, errno, "getsockopt packet statistics");
+
+ if (st.tp_packets != 1)
+ error(1, 0, "stats: tp_packets %u != 1", st.tp_packets);
+
+ if (st.tp_drops != 0)
+ error(1, 0, "stats: tp_drops %u != 0", st.tp_drops);
+
+ /* verify clear on read */
+ memset(&st, 0xff, sizeof(st));
+ len = sizeof(st);
+
+ if (getsockopt(fd, SOL_PACKET, PACKET_STATISTICS, &st, &len))
+ error(1, errno, "getsockopt packet statistics");
+
+ if (st.tp_packets != 0)
+ error(1, 0, "stats: tp_packets %u != 0 after clear", st.tp_packets);
+
+ if (st.tp_drops != 0)
+ error(1, 0, "stats: tp_drops %u != 0 after clear", st.tp_drops);
+}
+
static void run_test(void)
{
int fdr, fds, total_len;
@@ -369,9 +397,11 @@ static void run_test(void)
total_len = do_tx();
/* BPF filter accepts only this length, vlan changes MAC */
- if (cfg_payload_len == DATA_LEN && !cfg_use_vlan)
+ if (cfg_payload_len == DATA_LEN && !cfg_use_vlan) {
do_rx(fds, total_len - sizeof(struct virtio_net_hdr),
tbuf + sizeof(struct virtio_net_hdr));
+ check_packet_stats(fds);
+ }
do_rx(fdr, cfg_payload_len, tbuf + total_len - cfg_payload_len);
--
2.53.0-Meta
^ permalink raw reply related
* [PATCH net-next v3 0/3] Extend packet socket selftests
From: Joe Damato @ 2026-07-20 12:27 UTC (permalink / raw)
To: netdev
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, horms, willemb,
linux-kselftest, shuah, Joe Damato
Greetings:
I was looking around the packet socket code and noticed there were a few
features that could be tested by extending the existing packet socket
tests.
I extended the test to test stats, drops, and auxdata and re-ran the tests.
The existing and new tests passed.
Thanks,
Joe
v3:
- Patch 3 updated to prevent combining test_auxdata and test_drops.
- Patch 3 updated to include an aligned(8) for the cmsg to ensure (1) buffer
alignment for cmsg struct fields and (2) aligned payload access.
v2: https://lore.kernel.org/netdev/20260403233240.178948-1-joe@dama.to/
- patch 2: remove argument from do_tx and use global instead
- patch 3: add argument to do_rx to specify if the socket is a packet
socket. factored out the aux data check into its own function for
readability.
Joe Damato (3):
selftests/net: Test PACKET_STATISTICS
selftests/net: Test PACKET_STATISTICS drops
selftests/net: Test PACKET_AUXDATA
tools/testing/selftests/net/psock_snd.c | 140 +++++++++++++++++++++--
tools/testing/selftests/net/psock_snd.sh | 10 ++
2 files changed, 142 insertions(+), 8 deletions(-)
base-commit: 298bb2b8903323f6ef2eab4819a2e477765f0ff1
--
2.53.0-Meta
^ permalink raw reply
* Re: [PATCH] tty: ldisc: fix deadlock between ldisc_sem and rtnl_mutex
From: Zhou, Yun @ 2026-07-20 12:20 UTC (permalink / raw)
To: Greg KH
Cc: jirislaby, socketcan, linux-serial, mkl, linux-can, davem,
edumazet, kuba, pabeni, horms, netdev, linux-kernel
In-Reply-To: <2026071718-yearbook-bloated-48ce@gregkh>
On 7/17/26 18:53, Greg KH wrote:
> CAUTION: This email comes from a non Wind River email account!
> Do not click links or open attachments unless you recognize the sender and know the content is safe.
>
> On Thu, Jul 16, 2026 at 02:47:19PM +0800, Yun Zhou wrote:
>> syzbot reported a circular lock dependency involving tty ldisc_sem and
>> the networking rtnl_mutex. The full chain is:
>>
>> rtnl_mutex --> nft_commit_mutex --> ... --> ep->mtx --> ldisc_sem --> rtnl_mutex
>>
>> The last edge (ldisc_sem -> rtnl_mutex) is created because tty line
>> discipline .open() callbacks (slcan, slip) call register_netdev() which
>> acquires rtnl_mutex, and .open() runs under ldisc_sem write lock in
>> tty_set_ldisc().
>>
>> Fix by moving the .open() call outside the ldisc_sem write lock. The
>> ldisc .open() is initialization of the NEW discipline after the old one
>> has been closed - there is no need for ldisc_sem protection at this
>> point since:
>>
>> - tty_lock is held throughout, preventing concurrent tty_set_ldisc,
>> hangup, or close
>> - tty->ldisc is set to NULL during the window, so concurrent readers
>> (tty_ldisc_ref, tty_ldisc_ref_wait) see NULL and return immediately,
>> which callers already handle as a hangup condition
>> - tty buffer data stays queued until the ldisc is installed
>
> Ah, but look at the review at:
> https://sashiko.dev/#/patchset/20260716064719.1401892-1-yun.zhou@windriver.com
> which says:
> Does unlocking the semaphore while the ldisc pointer is NULL introduce a UAPI
> break for concurrent operations?
>
> If a concurrent process calls read(), write(), or poll() during this unlocked
> window, it can enter tty_ldisc_ref_wait() in drivers/tty/tty_io.c. Because the
> semaphore was unlocked here, tty_ldisc_ref_wait() will successfully acquire
> the read lock but observe tty->ldisc as NULL.
>
> This causes the reader to immediately return EOF or -EIO, potentially
> aborting userspace applications unexpectedly during a line discipline
> transition.
>
> Is that not true?
>
No, the issue reported by Sashiko does indeed exist. I will fix it in
the v2.
^ permalink raw reply
* Re: [PATCH net] ipv4: require matching source address for route hint reuse
From: Ido Schimmel @ 2026-07-20 12:16 UTC (permalink / raw)
To: Yizhou Zhao, pabeni
Cc: netdev, David Ahern, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, linux-kernel,
Yuxiang Yang, Ao Wang, Xuewei Feng, Qi Li, Ke Xu, stable
In-Reply-To: <20260714122618.21698-1-zhaoyz24@mails.tsinghua.edu.cn>
On Tue, Jul 14, 2026 at 08:26:17PM +0800, Yizhou Zhao wrote:
> IPv4 list receive can reuse a route from the previous skb in the same
> receive batch. The current eligibility check only compares the destination
> address and TOS before calling ip_route_use_hint().
>
> For forwarded routes, ip_route_use_hint() skips fib_validate_source()
> unless the hinted route is local. This means a packet with a different
> source address can reuse a forwarding dst created for an earlier packet
> and avoid source validation such as strict rp_filter.
I'm not sure why we are skipping source validation for non-local routes.
The comment above ip_route_use_hint() says "Implements all the
saddr-related checks as ip_route_input_slow()". I agree that
ip_route_input_slow() only does source validation for RTN_LOCAL, but for
RTN_UNICAST it is calling ip_mkroute_input(), which eventually calls
fib_validate_source().
Paolo, WDYT about always performing source validation [1]?
>
> In a KASAN QEMU router with strict rp_filter on the ingress device, a
Why mention KASAN? How is it related to this bug / patch?
> bad-only burst was dropped entirely, however, a paired valid/bad burst
> with the same destination/TOS made all of the bad packets pass rp_filter.
>
> Require the source address to match before reusing the hint. Packets from
> the same source/destination/TOS still take the fast path; packets whose
> source changes go through the normal route lookup and source validation
> path.
I agree that it fixes the problem, but we will always pay the
performance penalty, even when rp_filter is disabled. According to
commit 02b24941619f ("ipv4: use dst hint for ipv4 list receive"), there
is still a performance gain when we perform the source validation
per-packet.
[1]
diff --git a/net/ipv4/route.c b/net/ipv4/route.c
index 3f3de5164d6e..89338111793b 100644
--- a/net/ipv4/route.c
+++ b/net/ipv4/route.c
@@ -2194,6 +2194,7 @@ ip_route_use_hint(struct sk_buff *skb, __be32 daddr, __be32 saddr,
struct rtable *rt = skb_rtable(hint);
struct net *net = dev_net(dev);
u32 tag = 0;
+ int oif = 0;
if (!in_dev)
return reason;
@@ -2214,14 +2215,13 @@ ip_route_use_hint(struct sk_buff *skb, __be32 daddr, __be32 saddr,
}
if (!(rt->rt_flags & RTCF_LOCAL))
- goto skip_validate_source;
+ oif = dst_dev_rcu(&rt->dst)->ifindex;
- reason = fib_validate_source_reason(skb, saddr, daddr, dscp, 0, dev,
+ reason = fib_validate_source_reason(skb, saddr, daddr, dscp, oif, dev,
in_dev, &tag);
if (reason)
goto martian_source;
-skip_validate_source:
skb_dst_copy(skb, hint);
return SKB_NOT_DROPPED_YET;
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox