* [syzbot] [kernfs?] possible deadlock in kernfs_remove_by_name_ns (2)
From: syzbot @ 2026-07-19 10:02 UTC (permalink / raw)
To: driver-core, gregkh, linux-kernel, netdev, syzkaller-bugs, tj
Hello,
syzbot found the following issue on:
HEAD commit: 56d96fededd6 mpls: fix NULL deref in mpls_valid_fib_dump_r..
git tree: net
console output: https://syzkaller.appspot.com/x/log.txt?x=136d4746580000
kernel config: https://syzkaller.appspot.com/x/.config?x=5c4196ba0e33631d
dashboard link: https://syzkaller.appspot.com/bug?extid=b50eba6bf6cd3f8ba7d0
compiler: Debian clang version 22.1.8 (++20260613092233+e80beda6e255-1~exp1~20260613092250.77), Debian LLD 22.1.8
Unfortunately, I don't have any reproducer for this issue yet.
Downloadable assets:
disk image: https://storage.googleapis.com/syzbot-assets/8a219d5f8172/disk-56d96fed.raw.xz
vmlinux: https://storage.googleapis.com/syzbot-assets/bc2c4acbf659/vmlinux-56d96fed.xz
kernel image: https://storage.googleapis.com/syzbot-assets/2f3b2152262f/bzImage-56d96fed.xz
IMPORTANT: if you fix the issue, please add the following tag to the commit:
Reported-by: syzbot+b50eba6bf6cd3f8ba7d0@syzkaller.appspotmail.com
======================================================
WARNING: possible circular locking dependency detected
syzkaller #0 Not tainted
------------------------------------------------------
kworker/0:1/21181 is trying to acquire lock:
ffff88801c295180 (&root->kernfs_rwsem){++++}-{4:4}, at: kernfs_remove_by_name_ns+0x4e/0x140 fs/kernfs/dir.c:1792
but task is already holding lock:
ffff88801c2952a0 (&root->kernfs_supers_rwsem){++++}-{4:4}, at: kernfs_remove_by_name_ns+0x3f/0x140 fs/kernfs/dir.c:1791
which lock already depends on the new lock.
the existing dependency chain (in reverse order) is:
-> #10 (&root->kernfs_supers_rwsem){++++}-{4:4}:
down_read+0x4a/0x330 kernel/locking/rwsem.c:1574
kernfs_remove_by_name_ns+0x3f/0x140 fs/kernfs/dir.c:1791
kernfs_remove_by_name include/linux/kernfs.h:644 [inline]
create_files fs/sysfs/group.c:66 [inline]
internal_create_group+0x58e/0x1180 fs/sysfs/group.c:189
internal_create_groups fs/sysfs/group.c:229 [inline]
sysfs_update_groups+0x60/0x130 fs/sysfs/group.c:273
pmu_dev_alloc+0x29b/0x300 kernel/events/core.c:12772
perf_event_sysfs_init+0x76/0x100 kernel/events/core.c:15368
do_one_initcall+0x250/0x870 init/main.c:1347
do_initcall_level+0x10a/0x1a0 init/main.c:1409
do_initcalls+0x59/0xa0 init/main.c:1425
kernel_init_freeable+0x29d/0x3e0 init/main.c:1658
kernel_init+0x1d/0x1d0 init/main.c:1548
ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
-> #9 (pmus_lock){+.+.}-{4:4}:
__mutex_lock_common kernel/locking/mutex.c:646 [inline]
__mutex_lock+0x19d/0x1550 kernel/locking/mutex.c:821
perf_event_init_cpu+0x1d1/0x7a0 kernel/events/core.c:15270
cpuhp_invoke_callback+0x434/0x810 kernel/cpu.c:194
cpuhp_thread_fun+0x362/0x780 kernel/cpu.c:1109
smpboot_thread_fn+0x57c/0xa80 kernel/smpboot.c:160
kthread+0x388/0x470 kernel/kthread.c:436
ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
-> #8 (cpuhp_state-up){+.+.}-{0:0}:
cpuhp_lock_acquire kernel/cpu.c:103 [inline]
cpuhp_thread_fun+0x127/0x780 kernel/cpu.c:1086
smpboot_thread_fn+0x57c/0xa80 kernel/smpboot.c:160
kthread+0x388/0x470 kernel/kthread.c:436
ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
-> #7 (cpu_hotplug_lock){++++}-{0:0}:
percpu_down_read_internal include/linux/percpu-rwsem.h:53 [inline]
percpu_down_read include/linux/percpu-rwsem.h:77 [inline]
cpus_read_lock+0x42/0x160 kernel/cpu.c:490
static_key_slow_inc+0x12/0x30 kernel/jump_label.c:190
nbd_reconnect_socket drivers/block/nbd.c:1379 [inline]
nbd_genl_reconfigure+0x1301/0x1e80 drivers/block/nbd.c:2468
genl_family_rcv_msg_doit+0x233/0x340 net/netlink/genetlink.c:1114
genl_family_rcv_msg net/netlink/genetlink.c:1194 [inline]
genl_rcv_msg+0x614/0x7a0 net/netlink/genetlink.c:1209
netlink_rcv_skb+0x226/0x4a0 net/netlink/af_netlink.c:2556
genl_rcv+0x28/0x40 net/netlink/genetlink.c:1218
netlink_unicast_kernel net/netlink/af_netlink.c:1319 [inline]
netlink_unicast+0x7bb/0x940 net/netlink/af_netlink.c:1345
netlink_sendmsg+0x813/0xb40 net/netlink/af_netlink.c:1900
sock_sendmsg_nosec+0x13a/0x180 net/socket.c:775
__sock_sendmsg net/socket.c:790 [inline]
____sys_sendmsg+0x54e/0x850 net/socket.c:2684
___sys_sendmsg+0x2a5/0x360 net/socket.c:2738
__sys_sendmsg net/socket.c:2770 [inline]
__do_sys_sendmsg net/socket.c:2775 [inline]
__se_sys_sendmsg net/socket.c:2773 [inline]
__x64_sys_sendmsg+0x1b1/0x290 net/socket.c:2773
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
-> #6 (&nsock->tx_lock){+.+.}-{4:4}:
__mutex_lock_common kernel/locking/mutex.c:646 [inline]
__mutex_lock+0x19d/0x1550 kernel/locking/mutex.c:821
nbd_handle_cmd drivers/block/nbd.c:1143 [inline]
nbd_queue_rq+0x373/0x1150 drivers/block/nbd.c:1207
blk_mq_dispatch_rq_list+0x499/0x1990 block/blk-mq.c:2117
__blk_mq_do_dispatch_sched block/blk-mq-sched.c:168 [inline]
blk_mq_do_dispatch_sched block/blk-mq-sched.c:182 [inline]
__blk_mq_sched_dispatch_requests+0xd36/0x1580 block/blk-mq-sched.c:307
blk_mq_sched_dispatch_requests+0xd7/0x190 block/blk-mq-sched.c:329
blk_mq_run_work_fn+0x16c/0x300 block/blk-mq.c:2532
process_one_work kernel/workqueue.c:3322 [inline]
process_scheduled_works+0xa8e/0x14e0 kernel/workqueue.c:3405
worker_thread+0xa47/0xfb0 kernel/workqueue.c:3486
kthread+0x388/0x470 kernel/kthread.c:436
ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
-> #5 (&cmd->lock){+.+.}-{4:4}:
__mutex_lock_common kernel/locking/mutex.c:646 [inline]
__mutex_lock+0x19d/0x1550 kernel/locking/mutex.c:821
nbd_queue_rq+0xc1/0x1150 drivers/block/nbd.c:1199
blk_mq_dispatch_rq_list+0x499/0x1990 block/blk-mq.c:2117
__blk_mq_do_dispatch_sched block/blk-mq-sched.c:168 [inline]
blk_mq_do_dispatch_sched block/blk-mq-sched.c:182 [inline]
__blk_mq_sched_dispatch_requests+0xd36/0x1580 block/blk-mq-sched.c:307
blk_mq_sched_dispatch_requests+0xd7/0x190 block/blk-mq-sched.c:329
blk_mq_run_work_fn+0x16c/0x300 block/blk-mq.c:2532
process_one_work kernel/workqueue.c:3322 [inline]
process_scheduled_works+0xa8e/0x14e0 kernel/workqueue.c:3405
worker_thread+0xa47/0xfb0 kernel/workqueue.c:3486
kthread+0x388/0x470 kernel/kthread.c:436
ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
-> #4 (set->srcu){.+.+}-{0:0}:
srcu_lock_sync include/linux/srcu.h:199 [inline]
__synchronize_srcu+0xc9/0x2f0 kernel/rcu/srcutree.c:1481
elevator_switch+0x1e8/0x7b0 block/elevator.c:576
elevator_change+0x2fa/0x480 block/elevator.c:681
elevator_set_default+0x375/0x440 block/elevator.c:754
blk_register_queue+0x3f3/0x4e0 block/blk-sysfs.c:992
__add_disk+0x6cb/0xe30 block/genhd.c:528
add_disk_fwnode+0xfb/0x4b0 block/genhd.c:597
add_disk include/linux/blkdev.h:800 [inline]
nbd_dev_add+0x733/0xb60 drivers/block/nbd.c:2021
nbd_init+0x15f/0x1e0 drivers/block/nbd.c:2729
do_one_initcall+0x250/0x870 init/main.c:1347
do_initcall_level+0x10a/0x1a0 init/main.c:1409
do_initcalls+0x59/0xa0 init/main.c:1425
kernel_init_freeable+0x29d/0x3e0 init/main.c:1658
kernel_init+0x1d/0x1d0 init/main.c:1548
ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
-> #3 (&q->elevator_lock){+.+.}-{4:4}:
__mutex_lock_common kernel/locking/mutex.c:646 [inline]
__mutex_lock+0x19d/0x1550 kernel/locking/mutex.c:821
elevator_change+0x1af/0x480 block/elevator.c:679
elevator_set_none+0xb5/0x140 block/elevator.c:769
blk_mq_elv_switch_none block/blk-mq.c:5101 [inline]
__blk_mq_update_nr_hw_queues block/blk-mq.c:5146 [inline]
blk_mq_update_nr_hw_queues+0x5ef/0x19f0 block/blk-mq.c:5211
nbd_start_device+0x189/0xb30 drivers/block/nbd.c:1526
nbd_genl_connect+0x1597/0x1c10 drivers/block/nbd.c:2276
genl_family_rcv_msg_doit+0x233/0x340 net/netlink/genetlink.c:1114
genl_family_rcv_msg net/netlink/genetlink.c:1194 [inline]
genl_rcv_msg+0x614/0x7a0 net/netlink/genetlink.c:1209
netlink_rcv_skb+0x226/0x4a0 net/netlink/af_netlink.c:2556
genl_rcv+0x28/0x40 net/netlink/genetlink.c:1218
netlink_unicast_kernel net/netlink/af_netlink.c:1319 [inline]
netlink_unicast+0x7bb/0x940 net/netlink/af_netlink.c:1345
netlink_sendmsg+0x813/0xb40 net/netlink/af_netlink.c:1900
sock_sendmsg_nosec+0x13a/0x180 net/socket.c:775
__sock_sendmsg net/socket.c:790 [inline]
____sys_sendmsg+0x54e/0x850 net/socket.c:2684
___sys_sendmsg+0x2a5/0x360 net/socket.c:2738
__sys_sendmsg net/socket.c:2770 [inline]
__do_sys_sendmsg net/socket.c:2775 [inline]
__se_sys_sendmsg net/socket.c:2773 [inline]
__x64_sys_sendmsg+0x1b1/0x290 net/socket.c:2773
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
-> #2 (&q->q_usage_counter(io)#54){++++}-{0:0}:
blk_alloc_queue+0x544/0x690 block/blk-core.c:504
blk_mq_alloc_queue block/blk-mq.c:4420 [inline]
__blk_mq_alloc_disk+0x194/0x390 block/blk-mq.c:4467
nbd_dev_add+0x494/0xb60 drivers/block/nbd.c:1991
nbd_init+0x15f/0x1e0 drivers/block/nbd.c:2729
do_one_initcall+0x250/0x870 init/main.c:1347
do_initcall_level+0x10a/0x1a0 init/main.c:1409
do_initcalls+0x59/0xa0 init/main.c:1425
kernel_init_freeable+0x29d/0x3e0 init/main.c:1658
kernel_init+0x1d/0x1d0 init/main.c:1548
ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
-> #1 (fs_reclaim){+.+.}-{0:0}:
__fs_reclaim_acquire mm/page_alloc.c:4329 [inline]
fs_reclaim_acquire+0x71/0x100 mm/page_alloc.c:4343
might_alloc include/linux/sched/mm.h:317 [inline]
slab_pre_alloc_hook mm/slub.c:4565 [inline]
slab_alloc_node mm/slub.c:4925 [inline]
kmem_cache_alloc_lru_noprof+0x65/0x5f0 mm/slub.c:4978
alloc_inode+0xb8/0x1b0 fs/inode.c:340
iget_locked+0x131/0x6a0 fs/inode.c:1477
kernfs_get_inode+0x4f/0x770 fs/kernfs/inode.c:252
kernfs_fill_super fs/kernfs/mount.c:308 [inline]
kernfs_get_tree+0x5cd/0x980 fs/kernfs/mount.c:392
vfs_get_tree+0x92/0x2a0 fs/super.c:1694
fc_mount fs/namespace.c:1198 [inline]
do_new_mount_fc fs/namespace.c:3765 [inline]
do_new_mount+0x319/0xdc0 fs/namespace.c:3841
do_mount fs/namespace.c:4174 [inline]
__do_sys_mount fs/namespace.c:4390 [inline]
__se_sys_mount+0x31d/0x420 fs/namespace.c:4367
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
-> #0 (&root->kernfs_rwsem){++++}-{4:4}:
check_prev_add kernel/locking/lockdep.c:3165 [inline]
check_prevs_add kernel/locking/lockdep.c:3284 [inline]
validate_chain kernel/locking/lockdep.c:3908 [inline]
__lock_acquire+0x1520/0x2cf0 kernel/locking/lockdep.c:5237
lock_acquire+0x106/0x350 kernel/locking/lockdep.c:5868
down_write+0x96/0x200 kernel/locking/rwsem.c:1631
kernfs_remove_by_name_ns+0x4e/0x140 fs/kernfs/dir.c:1792
_cfg80211_unregister_wdev+0x133/0x5a0 net/wireless/core.c:1420
ieee80211_if_remove+0x289/0x340 net/mac80211/iface.c:2432
ieee80211_del_iface+0x19/0x30 net/mac80211/cfg.c:254
rdev_del_virtual_intf net/wireless/rdev-ops.h:62 [inline]
cfg80211_remove_virtual_intf+0x221/0x3f0 net/wireless/util.c:3006
cfg80211_destroy_ifaces+0x23f/0x300 net/wireless/core.c:436
cfg80211_destroy_iface_wk+0x21/0x30 net/wireless/core.c:464
process_one_work kernel/workqueue.c:3322 [inline]
process_scheduled_works+0xa8e/0x14e0 kernel/workqueue.c:3405
worker_thread+0xa47/0xfb0 kernel/workqueue.c:3486
kthread+0x388/0x470 kernel/kthread.c:436
ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
other info that might help us debug this:
Chain exists of:
&root->kernfs_rwsem --> pmus_lock --> &root->kernfs_supers_rwsem
Possible unsafe locking scenario:
CPU0 CPU1
---- ----
rlock(&root->kernfs_supers_rwsem);
lock(pmus_lock);
lock(&root->kernfs_supers_rwsem);
lock(&root->kernfs_rwsem);
*** DEADLOCK ***
5 locks held by kworker/0:1/21181:
#0: ffff88801b02b140 ((wq_completion)events){+.+.}-{0:0}, at: process_one_work kernel/workqueue.c:3297 [inline]
#0: ffff88801b02b140 ((wq_completion)events){+.+.}-{0:0}, at: process_scheduled_works+0xa20/0x14e0 kernel/workqueue.c:3405
#1: ffffc900041dfc40 ((work_completion)(&rdev->destroy_work)){+.+.}-{0:0}, at: process_one_work kernel/workqueue.c:3297 [inline]
#1: ffffc900041dfc40 ((work_completion)(&rdev->destroy_work)){+.+.}-{0:0}, at: process_scheduled_works+0xa20/0x14e0 kernel/workqueue.c:3405
#2: ffffffff9002c040 (rtnl_mutex){+.+.}-{4:4}, at: cfg80211_destroy_iface_wk+0x19/0x30 net/wireless/core.c:463
#3: ffff888088e907a0 (&rdev->wiphy.mtx){+.+.}-{4:4}, at: class_wiphy_constructor include/net/cfg80211.h:6884 [inline]
#3: ffff888088e907a0 (&rdev->wiphy.mtx){+.+.}-{4:4}, at: cfg80211_destroy_ifaces+0x233/0x300 net/wireless/core.c:434
#4: ffff88801c2952a0 (&root->kernfs_supers_rwsem){++++}-{4:4}, at: kernfs_remove_by_name_ns+0x3f/0x140 fs/kernfs/dir.c:1791
stack backtrace:
CPU: 0 UID: 0 PID: 21181 Comm: kworker/0:1 Not tainted syzkaller #0 PREEMPT(full)
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 05/09/2026
Workqueue: events cfg80211_destroy_iface_wk
Call Trace:
<TASK>
dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
print_circular_bug+0x2e1/0x300 kernel/locking/lockdep.c:2043
check_noncircular+0x12e/0x150 kernel/locking/lockdep.c:2175
check_prev_add kernel/locking/lockdep.c:3165 [inline]
check_prevs_add kernel/locking/lockdep.c:3284 [inline]
validate_chain kernel/locking/lockdep.c:3908 [inline]
__lock_acquire+0x1520/0x2cf0 kernel/locking/lockdep.c:5237
lock_acquire+0x106/0x350 kernel/locking/lockdep.c:5868
down_write+0x96/0x200 kernel/locking/rwsem.c:1631
kernfs_remove_by_name_ns+0x4e/0x140 fs/kernfs/dir.c:1792
_cfg80211_unregister_wdev+0x133/0x5a0 net/wireless/core.c:1420
ieee80211_if_remove+0x289/0x340 net/mac80211/iface.c:2432
ieee80211_del_iface+0x19/0x30 net/mac80211/cfg.c:254
rdev_del_virtual_intf net/wireless/rdev-ops.h:62 [inline]
cfg80211_remove_virtual_intf+0x221/0x3f0 net/wireless/util.c:3006
cfg80211_destroy_ifaces+0x23f/0x300 net/wireless/core.c:436
cfg80211_destroy_iface_wk+0x21/0x30 net/wireless/core.c:464
process_one_work kernel/workqueue.c:3322 [inline]
process_scheduled_works+0xa8e/0x14e0 kernel/workqueue.c:3405
worker_thread+0xa47/0xfb0 kernel/workqueue.c:3486
kthread+0x388/0x470 kernel/kthread.c:436
ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
</TASK>
---
This report is generated by a bot. It may contain errors.
See https://goo.gl/tpsmEJ for more information about syzbot.
syzbot engineers can be reached at syzkaller@googlegroups.com.
syzbot will keep track of this issue. See:
https://goo.gl/tpsmEJ#status for how to communicate with syzbot.
If the report is already addressed, let syzbot know by replying with:
#syz fix: exact-commit-title
If you want to overwrite report's subsystems, reply with:
#syz set subsystems: new-subsystem
(See the list of subsystem names on the web dashboard)
If the report is a duplicate of another one, reply with:
#syz dup: exact-subject-of-another-report
If you want to undo deduplication, reply with:
#syz undup
^ permalink raw reply
* Re: [PATCH net-next v2 0/2] net: dsa: mv88e6xxx: various hwstamp fixes
From: Vladimir Oltean @ 2026-07-19 9:53 UTC (permalink / raw)
To: Luke Howard
Cc: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Vivien Didelot, Gregory CLEMENT, Andrew Lunn, Richard Cochran,
Cedric Jehasse, Kieran Tyrrell, Max Holtmann, Max Hunter,
Christoph Mellauner, Simon Gapp, netdev, linux-kernel
In-Reply-To: <0A7B1CC8-F5F6-487E-A14C-1F0C2D5ADC84@padl.com>
On Fri, Jul 17, 2026 at 05:47:49PM +1000, Luke Howard wrote:
>
> > ---
> > Luke Howard (2):
> > net: dsa: mv88e6xxx: embedded PTP timestamp support
>
> This can be improved: there is no need to extract the embedded
> timestamp in the PTP worker as it can be done directly in
> mv88e6xxx_port_rxtstamp() (provided tstamp_{cc,tc} are protected by a
> spinlock). DSA can deliver the frame normally, similar to
> ocelot_ptp_rx_timestamp().
ocelot_ptp_rx_timestamp() accesses MMIO-based registers, which can be
done atomically.
mv88e6xxx_ptp_clock_read() accesses MDIO bus registers, and the MDIO bus
is sleepable. Fundamental difference.
Your hardware only provides 32 bits of partial timestamp, so
mv88e6xxx_ptp_clock_read() will always be needed one way or another, to
recover the full 64 bits. Either through tstamp_{cc,tc} or through
direct calls.
> Deferring to the worker can reorder frames such that PTP general
> messages arrive before the timestamped event messages, which confuses
> some other PTP implementations such as gptp2d [1].
True, this is a caveat, but event messages and general messages can
already take different network paths, especially with PTP over IP where
they go through different UDP ports (even if for gPTP that is not the case).
The PTP user space implementation needs to be prepared to handle this.
> This optimisation of course only works for ArrTSMode because there is
> no MDIO read required.
I don't understand this comment given the partial 32-bit timestamp
limitation.
> Luke
>
> [1] https://github.com/xl4-shiro/excelfore-gptp
^ permalink raw reply
* [PATCH ethtool-next v3 3/3] qsfp: print all compliance codes
From: Aleksander Jan Bajkowski @ 2026-07-19 9:00 UTC (permalink / raw)
To: danieller, olek2, mkubecek, andrew, davem, edumazet, kuba, pabeni,
jbe, netdev
In-Reply-To: <20260719090458.659332-1-olek2@wp.pl>
QSFP modules implement multiple compliance codes. This is common for
dual-rate modules. Before the `json` option was introduced, all
compliance codes were displayed. Currently, only the last code is
displayed. This commit fixes that bug. Compliance codes are
represented as array.
Fixes: 4071862f58d8 ("sfpid: Add JSON output handling to --module-info in SFF8079 modules")
Signed-off-by: Aleksander Jan Bajkowski <olek2@wp.pl>
---
Changes in v3:
- add patch to series
---
qsfp.c | 264 +++++++++++++++++++++++++++++----------------------------
1 file changed, 134 insertions(+), 130 deletions(-)
diff --git a/qsfp.c b/qsfp.c
index c82a3de..113663a 100644
--- a/qsfp.c
+++ b/qsfp.c
@@ -182,7 +182,6 @@ static void sff8636_show_connector(const struct sff8636_memory_map *map)
static void sff8636_show_transceiver(const struct sff8636_memory_map *map)
{
static const char *pfx = "Transceiver type";
- char value[140] = "";
if (is_json_context()) {
open_json_array("transceiver_codes", "");
@@ -217,226 +216,230 @@ static void sff8636_show_transceiver(const struct sff8636_memory_map *map)
map->page_00h[SFF8636_FC_SPEED_OFFSET]);
}
+ if (is_json_context())
+ open_json_array("transceiver_type", "");
+
/* 10G/40G Ethernet Compliance Codes */
if (map->page_00h[SFF8636_ETHERNET_COMP_OFFSET] &
SFF8636_ETHERNET_10G_LRM)
- sprintf(value, "%s", "10G Ethernet: 10G Base-LRM");
+ module_print_any_array_string_entry(pfx, "10G Ethernet: 10G Base-LRM");
if (map->page_00h[SFF8636_ETHERNET_COMP_OFFSET] &
SFF8636_ETHERNET_10G_LR)
- sprintf(value, "%s", "10G Ethernet: 10G Base-LR");
+ module_print_any_array_string_entry(pfx, "10G Ethernet: 10G Base-LR");
if (map->page_00h[SFF8636_ETHERNET_COMP_OFFSET] &
SFF8636_ETHERNET_10G_SR)
- sprintf(value, "%s", "10G Ethernet: 10G Base-SR");
+ module_print_any_array_string_entry(pfx, "10G Ethernet: 10G Base-SR");
if (map->page_00h[SFF8636_ETHERNET_COMP_OFFSET] &
SFF8636_ETHERNET_40G_CR4)
- sprintf(value, "%s", "40G Ethernet: 40G Base-CR4");
+ module_print_any_array_string_entry(pfx, "40G Ethernet: 40G Base-CR4");
if (map->page_00h[SFF8636_ETHERNET_COMP_OFFSET] &
SFF8636_ETHERNET_40G_SR4)
- sprintf(value, "%s", "40G Ethernet: 40G Base-SR4");
+ module_print_any_array_string_entry(pfx, "40G Ethernet: 40G Base-SR4");
if (map->page_00h[SFF8636_ETHERNET_COMP_OFFSET] &
SFF8636_ETHERNET_40G_LR4)
- sprintf(value, "%s", "40G Ethernet: 40G Base-LR4");
+ module_print_any_array_string_entry(pfx, "40G Ethernet: 40G Base-LR4");
if (map->page_00h[SFF8636_ETHERNET_COMP_OFFSET] &
SFF8636_ETHERNET_40G_ACTIVE)
- sprintf(value, "%s", "40G Ethernet: 40G Active Cable (XLPPI)");
+ module_print_any_array_string_entry(pfx, "40G Ethernet: 40G Active Cable (XLPPI)");
/* Extended Specification Compliance Codes from SFF-8024 */
if (map->page_00h[SFF8636_ETHERNET_COMP_OFFSET] &
SFF8636_ETHERNET_RSRVD) {
switch (map->page_00h[SFF8636_OPTION_1_OFFSET]) {
case SFF8636_ETHERNET_UNSPECIFIED:
- sprintf(value, "%s", "(reserved or unknown)");
+ module_print_any_array_string_entry(pfx, "(reserved or unknown)");
break;
case SFF8636_ETHERNET_100G_AOC:
- sprintf(value, "%s",
- "100G Ethernet: 100G AOC or 25GAUI C2M AOC with worst BER of 5x10^(-5)");
+ module_print_any_array_string_entry(pfx,
+ "100G Ethernet: 100G AOC or 25GAUI C2M AOC with worst BER of 5x10^(-5)");
break;
case SFF8636_ETHERNET_100G_SR4:
- sprintf(value, "%s",
- "100G Ethernet: 100G Base-SR4 or 25GBase-SR");
+ module_print_any_array_string_entry(pfx,
+ "100G Ethernet: 100G Base-SR4 or 25GBase-SR");
break;
case SFF8636_ETHERNET_100G_LR4:
- sprintf(value, "%s", "100G Ethernet: 100G Base-LR4");
+ module_print_any_array_string_entry(pfx, "100G Ethernet: 100G Base-LR4");
break;
case SFF8636_ETHERNET_100G_ER4:
- sprintf(value, "%s", "100G Ethernet: 100G Base-ER4");
+ module_print_any_array_string_entry(pfx, "100G Ethernet: 100G Base-ER4");
break;
case SFF8636_ETHERNET_100G_SR10:
- sprintf(value, "%s", "100G Ethernet: 100G Base-SR10");
+ module_print_any_array_string_entry(pfx, "100G Ethernet: 100G Base-SR10");
break;
case SFF8636_ETHERNET_100G_CWDM4_FEC:
- sprintf(value, "%s",
- "100G Ethernet: 100G CWDM4 MSA with FEC");
+ module_print_any_array_string_entry(pfx,
+ "100G Ethernet: 100G CWDM4 MSA with FEC");
break;
case SFF8636_ETHERNET_100G_PSM4:
- sprintf(value, "%s",
- "100G Ethernet: 100G PSM4 Parallel SMF");
+ module_print_any_array_string_entry(pfx,
+ "100G Ethernet: 100G PSM4 Parallel SMF");
break;
case SFF8636_ETHERNET_100G_ACC:
- sprintf(value, "%s",
- "100G Ethernet: 100G ACC or 25GAUI C2M ACC with worst BER of 5x10^(-5)");
+ module_print_any_array_string_entry(pfx,
+ "100G Ethernet: 100G ACC or 25GAUI C2M ACC with worst BER of 5x10^(-5)");
break;
case SFF8636_ETHERNET_100G_CWDM4_NO_FEC:
- sprintf(value, "%s",
- "100G Ethernet: 100G CWDM4 MSA without FEC");
+ module_print_any_array_string_entry(pfx,
+ "100G Ethernet: 100G CWDM4 MSA without FEC");
break;
case SFF8636_ETHERNET_100G_RSVD1:
- sprintf(value, "%s", "(reserved or unknown)");
+ module_print_any_array_string_entry(pfx, "(reserved or unknown)");
break;
case SFF8636_ETHERNET_100G_CR4:
- sprintf(value, "%s",
- "100G Ethernet: 100G Base-CR4 or 25G Base-CR CA-L");
+ module_print_any_array_string_entry(pfx,
+ "100G Ethernet: 100G Base-CR4 or 25G Base-CR CA-L");
break;
case SFF8636_ETHERNET_25G_CR_CA_S:
- sprintf(value, "%s", "25G Ethernet: 25G Base-CR CA-S");
+ module_print_any_array_string_entry(pfx, "25G Ethernet: 25G Base-CR CA-S");
break;
case SFF8636_ETHERNET_25G_CR_CA_N:
- sprintf(value, "%s", "25G Ethernet: 25G Base-CR CA-N");
+ module_print_any_array_string_entry(pfx, "25G Ethernet: 25G Base-CR CA-N");
break;
case SFF8636_ETHERNET_40G_ER4:
- sprintf(value, "%s", "40G Ethernet: 40G Base-ER4");
+ module_print_any_array_string_entry(pfx, "40G Ethernet: 40G Base-ER4");
break;
case SFF8636_ETHERNET_4X10_SR:
- sprintf(value, "%s", "4x10G Ethernet: 10G Base-SR");
+ module_print_any_array_string_entry(pfx, "4x10G Ethernet: 10G Base-SR");
break;
case SFF8636_ETHERNET_40G_PSM4:
- sprintf(value, "%s",
- "40G Ethernet: 40G PSM4 Parallel SMF");
+ module_print_any_array_string_entry(pfx,
+ "40G Ethernet: 40G PSM4 Parallel SMF");
break;
case SFF8636_ETHERNET_G959_P1I1_2D1:
- sprintf(value, "%s",
- "Ethernet: G959.1 profile P1I1-2D1 (10709 MBd, 2km, 1310nm SM)");
+ module_print_any_array_string_entry(pfx,
+ "Ethernet: G959.1 profile P1I1-2D1 (10709 MBd, 2km, 1310nm SM)");
break;
case SFF8636_ETHERNET_G959_P1S1_2D2:
- sprintf(value, "%s",
- "Ethernet: G959.1 profile P1S1-2D2 (10709 MBd, 40km, 1550nm SM)");
+ module_print_any_array_string_entry(pfx,
+ "Ethernet: G959.1 profile P1S1-2D2 (10709 MBd, 40km, 1550nm SM)");
break;
case SFF8636_ETHERNET_G959_P1L1_2D2:
- sprintf(value, "%s",
- "Ethernet: G959.1 profile P1L1-2D2 (10709 MBd, 80km, 1550nm SM)");
+ module_print_any_array_string_entry(pfx,
+ "Ethernet: G959.1 profile P1L1-2D2 (10709 MBd, 80km, 1550nm SM)");
break;
case SFF8636_ETHERNET_10GT_SFI:
- sprintf(value, "%s",
- "10G Ethernet: 10G Base-T with SFI electrical interface");
+ module_print_any_array_string_entry(pfx,
+ "10G Ethernet: 10G Base-T with SFI electrical interface");
break;
case SFF8636_ETHERNET_100G_CLR4:
- sprintf(value, "%s", "100G Ethernet: 100G CLR4");
+ module_print_any_array_string_entry(pfx, "100G Ethernet: 100G CLR4");
break;
case SFF8636_ETHERNET_100G_AOC2:
- sprintf(value, "%s",
- "100G Ethernet: 100G AOC or 25GAUI C2M AOC with worst BER of 10^(-12)");
+ module_print_any_array_string_entry(pfx,
+ "100G Ethernet: 100G AOC or 25GAUI C2M AOC with worst BER of 10^(-12)");
break;
case SFF8636_ETHERNET_100G_ACC2:
- sprintf(value, "%s",
- "100G Ethernet: 100G ACC or 25GAUI C2M ACC with worst BER of 10^(-12)");
+ module_print_any_array_string_entry(pfx,
+ "100G Ethernet: 100G ACC or 25GAUI C2M ACC with worst BER of 10^(-12)");
break;
case SFF8636_ETHERNET_100GE_DWDM2:
- sprintf(value, "%s",
- "100GE-DWDM2 (DWDM transceiver using 2 wavelengths on a 1550 nm DWDM grid with a reach up to 80 km)");
+ module_print_any_array_string_entry(pfx,
+ "100GE-DWDM2 (DWDM transceiver using 2 wavelengths on a 1550 nm DWDM grid with a reach up to 80 km)");
break;
case SFF8636_ETHERNET_100G_1550NM_WDM:
- sprintf(value, "%s", "100G 1550nm WDM (4 wavelengths)");
+ module_print_any_array_string_entry(pfx, "100G 1550nm WDM (4 wavelengths)");
break;
case SFF8636_ETHERNET_10G_BASET_SR:
- sprintf(value, "%s",
- "10GBASE-T Short Reach (30 meters)");
+ module_print_any_array_string_entry(pfx,
+ "10GBASE-T Short Reach (30 meters)");
break;
case SFF8636_ETHERNET_5G_BASET:
- sprintf(value, "%s", "5GBASE-T");
+ module_print_any_array_string_entry(pfx, "5GBASE-T");
break;
case SFF8636_ETHERNET_2HALFG_BASET:
- sprintf(value, "%s", "2.5GBASE-T");
+ module_print_any_array_string_entry(pfx, "2.5GBASE-T");
break;
case SFF8636_ETHERNET_40G_SWDM4:
- sprintf(value, "%s", "40G SWDM4");
+ module_print_any_array_string_entry(pfx, "40G SWDM4");
break;
case SFF8636_ETHERNET_100G_SWDM4:
- sprintf(value, "%s", "100G SWDM4");
+ module_print_any_array_string_entry(pfx, "100G SWDM4");
break;
case SFF8636_ETHERNET_100G_PAM4_BIDI:
- sprintf(value, "%s", "100G PAM4 BiDi");
+ module_print_any_array_string_entry(pfx, "100G PAM4 BiDi");
break;
case SFF8636_ETHERNET_4WDM10_MSA:
- sprintf(value, "%s",
- "4WDM-10 MSA (10km version of 100G CWDM4 with same RS(528,514) FEC in host system)");
+ module_print_any_array_string_entry(pfx,
+ "4WDM-10 MSA (10km version of 100G CWDM4 with same RS(528,514) FEC in host system)");
break;
case SFF8636_ETHERNET_4WDM20_MSA:
- sprintf(value, "%s", "4WDM-20 MSA (20km version of 100GBASE-LR4 with RS(528,514) FEC in host system)");
+ module_print_any_array_string_entry(pfx,
+ "4WDM-20 MSA (20km version of 100GBASE-LR4 with RS(528,514) FEC in host system)");
break;
case SFF8636_ETHERNET_4WDM40_MSA:
- sprintf(value, "%s",
- "4WDM-40 MSA (40km reach with APD receiver and RS(528,514) FEC in host system)");
+ module_print_any_array_string_entry(pfx,
+ "4WDM-40 MSA (40km reach with APD receiver and RS(528,514) FEC in host system)");
break;
case SFF8636_ETHERNET_100G_DR:
- sprintf(value, "%s",
- "100GBASE-DR (clause 140), CAUI-4 (no FEC)");
+ module_print_any_array_string_entry(pfx,
+ "100GBASE-DR (clause 140), CAUI-4 (no FEC)");
break;
case SFF8636_ETHERNET_100G_FR_NOFEC:
- sprintf(value, "%s",
- "100G-FR or 100GBASE-FR1 (clause 140), CAUI-4 (no FEC)");
+ module_print_any_array_string_entry(pfx,
+ "100G-FR or 100GBASE-FR1 (clause 140), CAUI-4 (no FEC)");
break;
case SFF8636_ETHERNET_100G_LR_NOFEC:
- sprintf(value, "%s",
- "100G-LR or 100GBASE-LR1 (clause 140), CAUI-4 (no FEC)");
+ module_print_any_array_string_entry(pfx,
+ "100G-LR or 100GBASE-LR1 (clause 140), CAUI-4 (no FEC)");
break;
case SFF8636_ETHERNET_200G_ACC1:
- sprintf(value, "%s",
- "Active Copper Cable with 50GAUI, 100GAUI-2 or 200GAUI-4 C2M. Providing a worst BER of 10-6 or below");
+ module_print_any_array_string_entry(pfx,
+ "Active Copper Cable with 50GAUI, 100GAUI-2 or 200GAUI-4 C2M. Providing a worst BER of 10-6 or below");
break;
case SFF8636_ETHERNET_200G_AOC1:
- sprintf(value, "%s",
- "Active Optical Cable with 50GAUI, 100GAUI-2 or 200GAUI-4 C2M. Providing a worst BER of 10-6 or below");
+ module_print_any_array_string_entry(pfx,
+ "Active Optical Cable with 50GAUI, 100GAUI-2 or 200GAUI-4 C2M. Providing a worst BER of 10-6 or below");
break;
case SFF8636_ETHERNET_200G_ACC2:
- sprintf(value, "%s",
- "Active Copper Cable with 50GAUI, 100GAUI-2 or 200GAUI-4 C2M. Providing a worst BER of 2.6x10-4 for ACC, 10-5 for AUI, or below");
+ module_print_any_array_string_entry(pfx,
+ "Active Copper Cable with 50GAUI, 100GAUI-2 or 200GAUI-4 C2M. Providing a worst BER of 2.6x10-4 for ACC, 10-5 for AUI, or below");
break;
case SFF8636_ETHERNET_200G_A0C2:
- sprintf(value, "%s",
- "Active Optical Cable with 50GAUI, 100GAUI-2 or 200GAUI-4 C2M. Providing a worst BER of 2.6x10-4 for ACC, 10-5 for AUI, or below");
+ module_print_any_array_string_entry(pfx,
+ "Active Optical Cable with 50GAUI, 100GAUI-2 or 200GAUI-4 C2M. Providing a worst BER of 2.6x10-4 for ACC, 10-5 for AUI, or below");
break;
case SFF8636_ETHERNET_200G_CR4:
- sprintf(value, "%s",
- "50GBASE-CR, 100GBASE-CR2, or 200GBASE-CR4");
+ module_print_any_array_string_entry(pfx,
+ "50GBASE-CR, 100GBASE-CR2, or 200GBASE-CR4");
break;
case SFF8636_ETHERNET_200G_SR4:
- sprintf(value, "%s",
- "50GBASE-SR, 100GBASE-SR2, or 200GBASE-SR4");
+ module_print_any_array_string_entry(pfx,
+ "50GBASE-SR, 100GBASE-SR2, or 200GBASE-SR4");
break;
case SFF8636_ETHERNET_200G_DR4:
- sprintf(value, "%s", "50GBASE-FR or 200GBASE-DR4");
+ module_print_any_array_string_entry(pfx, "50GBASE-FR or 200GBASE-DR4");
break;
case SFF8636_ETHERNET_200G_FR4:
- sprintf(value, "%s", "200GBASE-FR4");
+ module_print_any_array_string_entry(pfx, "200GBASE-FR4");
break;
case SFF8636_ETHERNET_200G_PSM4:
- sprintf(value, "%s", "200G 1550 nm PSM4");
+ module_print_any_array_string_entry(pfx, "200G 1550 nm PSM4");
break;
case SFF8636_ETHERNET_50G_LR:
- sprintf(value, "%s", "50GBASE-LR");
+ module_print_any_array_string_entry(pfx, "50GBASE-LR");
break;
case SFF8636_ETHERNET_200G_LR4:
- sprintf(value, "%s", "200GBASE-LR4");
+ module_print_any_array_string_entry(pfx, "200GBASE-LR4");
break;
case SFF8636_ETHERNET_64G_EA:
- sprintf(value, "%s", "64GFC EA");
+ module_print_any_array_string_entry(pfx, "64GFC EA");
break;
case SFF8636_ETHERNET_64G_SW:
- sprintf(value, "%s", "64GFC SW");
+ module_print_any_array_string_entry(pfx, "64GFC SW");
break;
case SFF8636_ETHERNET_64G_LW:
- sprintf(value, "%s", "64GFC LW");
+ module_print_any_array_string_entry(pfx, "64GFC LW");
break;
case SFF8636_ETHERNET_128FC_EA:
- sprintf(value, "%s", "128GFC EA");
+ module_print_any_array_string_entry(pfx, "128GFC EA");
break;
case SFF8636_ETHERNET_128FC_SW:
- sprintf(value, "%s", "128GFC SW");
+ module_print_any_array_string_entry(pfx, "128GFC SW");
break;
case SFF8636_ETHERNET_128FC_LW:
- sprintf(value, "%s", "128GFC LW");
+ module_print_any_array_string_entry(pfx, "128GFC LW");
break;
default:
- sprintf(value, "%s", "(reserved or unknown)");
+ module_print_any_array_string_entry(pfx, "(reserved or unknown)");
break;
}
}
@@ -444,104 +447,105 @@ static void sff8636_show_transceiver(const struct sff8636_memory_map *map)
/* SONET Compliance Codes */
if (map->page_00h[SFF8636_SONET_COMP_OFFSET] &
(SFF8636_SONET_40G_OTN))
- sprintf(value, "%s", "40G OTN (OTU3B/OTU3C)");
+ module_print_any_array_string_entry(pfx, "40G OTN (OTU3B/OTU3C)");
if (map->page_00h[SFF8636_SONET_COMP_OFFSET] & (SFF8636_SONET_OC48_LR))
- sprintf(value, "%s", "SONET: OC-48, long reach");
+ module_print_any_array_string_entry(pfx, "SONET: OC-48, long reach");
if (map->page_00h[SFF8636_SONET_COMP_OFFSET] & (SFF8636_SONET_OC48_IR))
- sprintf(value, "%s", "SONET: OC-48, intermediate reach");
+ module_print_any_array_string_entry(pfx, "SONET: OC-48, intermediate reach");
if (map->page_00h[SFF8636_SONET_COMP_OFFSET] & (SFF8636_SONET_OC48_SR))
- sprintf(value, "%s", "SONET: OC-48, short reach");
+ module_print_any_array_string_entry(pfx, "SONET: OC-48, short reach");
/* SAS/SATA Compliance Codes */
if (map->page_00h[SFF8636_SAS_COMP_OFFSET] & (SFF8636_SAS_24G))
- sprintf(value, "%s", "SAS 24.0G");
+ module_print_any_array_string_entry(pfx, "SAS 24.0G");
if (map->page_00h[SFF8636_SAS_COMP_OFFSET] & (SFF8636_SAS_12G))
- sprintf(value, "%s", "SAS 12.0G");
+ module_print_any_array_string_entry(pfx, "SAS 12.0G");
if (map->page_00h[SFF8636_SAS_COMP_OFFSET] & (SFF8636_SAS_6G))
- sprintf(value, "%s", "SAS 6.0G");
+ module_print_any_array_string_entry(pfx, "SAS 6.0G");
if (map->page_00h[SFF8636_SAS_COMP_OFFSET] & (SFF8636_SAS_3G))
- sprintf(value, "%s", "SAS 3.0G");
+ module_print_any_array_string_entry(pfx, "SAS 3.0G");
/* Ethernet Compliance Codes */
if (map->page_00h[SFF8636_GIGE_COMP_OFFSET] & SFF8636_GIGE_1000_BASE_T)
- sprintf(value, "%s", "Ethernet: 1000BASE-T");
+ module_print_any_array_string_entry(pfx, "Ethernet: 1000BASE-T");
if (map->page_00h[SFF8636_GIGE_COMP_OFFSET] & SFF8636_GIGE_1000_BASE_CX)
- sprintf(value, "%s", "Ethernet: 1000BASE-CX");
+ module_print_any_array_string_entry(pfx, "Ethernet: 1000BASE-CX");
if (map->page_00h[SFF8636_GIGE_COMP_OFFSET] & SFF8636_GIGE_1000_BASE_LX)
- sprintf(value, "%s", "Ethernet: 1000BASE-LX");
+ module_print_any_array_string_entry(pfx, "Ethernet: 1000BASE-LX");
if (map->page_00h[SFF8636_GIGE_COMP_OFFSET] & SFF8636_GIGE_1000_BASE_SX)
- sprintf(value, "%s", "Ethernet: 1000BASE-SX");
+ module_print_any_array_string_entry(pfx, "Ethernet: 1000BASE-SX");
/* Fibre Channel link length */
if (map->page_00h[SFF8636_FC_LEN_OFFSET] & SFF8636_FC_LEN_VERY_LONG)
- sprintf(value, "%s", "FC: very long distance (V)");
+ module_print_any_array_string_entry(pfx, "FC: very long distance (V)");
if (map->page_00h[SFF8636_FC_LEN_OFFSET] & SFF8636_FC_LEN_SHORT)
- sprintf(value, "%s", "FC: short distance (S)");
+ module_print_any_array_string_entry(pfx, "FC: short distance (S)");
if (map->page_00h[SFF8636_FC_LEN_OFFSET] & SFF8636_FC_LEN_INT)
- sprintf(value, "%s", "FC: intermediate distance (I)");
+ module_print_any_array_string_entry(pfx, "FC: intermediate distance (I)");
if (map->page_00h[SFF8636_FC_LEN_OFFSET] & SFF8636_FC_LEN_LONG)
- sprintf(value, "%s", "FC: long distance (L)");
+ module_print_any_array_string_entry(pfx, "FC: long distance (L)");
if (map->page_00h[SFF8636_FC_LEN_OFFSET] & SFF8636_FC_LEN_MED)
- sprintf(value, "%s", "FC: medium distance (M)");
+ module_print_any_array_string_entry(pfx, "FC: medium distance (M)");
/* Fibre Channel transmitter technology */
if (map->page_00h[SFF8636_FC_LEN_OFFSET] & SFF8636_FC_TECH_LONG_LC)
- sprintf(value, "%s", "FC: Longwave laser (LC)");
+ module_print_any_array_string_entry(pfx, "FC: Longwave laser (LC)");
if (map->page_00h[SFF8636_FC_LEN_OFFSET] & SFF8636_FC_TECH_ELEC_INTER)
- sprintf(value, "%s", "FC: Electrical inter-enclosure (EL)");
+ module_print_any_array_string_entry(pfx, "FC: Electrical inter-enclosure (EL)");
if (map->page_00h[SFF8636_FC_TECH_OFFSET] & SFF8636_FC_TECH_ELEC_INTRA)
- sprintf(value, "%s", "FC: Electrical intra-enclosure (EL)");
+ module_print_any_array_string_entry(pfx, "FC: Electrical intra-enclosure (EL)");
if (map->page_00h[SFF8636_FC_TECH_OFFSET] &
SFF8636_FC_TECH_SHORT_WO_OFC)
- sprintf(value, "%s", "FC: Shortwave laser w/o OFC (SN)");
+ module_print_any_array_string_entry(pfx, "FC: Shortwave laser w/o OFC (SN)");
if (map->page_00h[SFF8636_FC_TECH_OFFSET] & SFF8636_FC_TECH_SHORT_W_OFC)
- sprintf(value, "%s", "FC: Shortwave laser with OFC (SL)");
+ module_print_any_array_string_entry(pfx, "FC: Shortwave laser with OFC (SL)");
if (map->page_00h[SFF8636_FC_TECH_OFFSET] & SFF8636_FC_TECH_LONG_LL)
- sprintf(value, "%s", "FC: Longwave laser (LL)");
+ module_print_any_array_string_entry(pfx, "FC: Longwave laser (LL)");
/* Fibre Channel transmission media */
if (map->page_00h[SFF8636_FC_TRANS_MEDIA_OFFSET] &
SFF8636_FC_TRANS_MEDIA_TW)
- sprintf(value, "%s", "FC: Twin Axial Pair (TW)");
+ module_print_any_array_string_entry(pfx, "FC: Twin Axial Pair (TW)");
if (map->page_00h[SFF8636_FC_TRANS_MEDIA_OFFSET] &
SFF8636_FC_TRANS_MEDIA_TP)
- sprintf(value, "%s", "FC: Twisted Pair (TP)");
+ module_print_any_array_string_entry(pfx, "FC: Twisted Pair (TP)");
if (map->page_00h[SFF8636_FC_TRANS_MEDIA_OFFSET] &
SFF8636_FC_TRANS_MEDIA_MI)
- sprintf(value, "%s", "FC: Miniature Coax (MI)");
+ module_print_any_array_string_entry(pfx, "FC: Miniature Coax (MI)");
if (map->page_00h[SFF8636_FC_TRANS_MEDIA_OFFSET] &
SFF8636_FC_TRANS_MEDIA_TV)
- sprintf(value, "%s", "FC: Video Coax (TV)");
+ module_print_any_array_string_entry(pfx, "FC: Video Coax (TV)");
if (map->page_00h[SFF8636_FC_TRANS_MEDIA_OFFSET] &
SFF8636_FC_TRANS_MEDIA_M6)
- sprintf(value, "%s", "FC: Multimode, 62.5m (M6)");
+ module_print_any_array_string_entry(pfx, "FC: Multimode, 62.5m (M6)");
if (map->page_00h[SFF8636_FC_TRANS_MEDIA_OFFSET] &
SFF8636_FC_TRANS_MEDIA_M5)
- sprintf(value, "%s", "FC: Multimode, 50m (M5)");
+ module_print_any_array_string_entry(pfx, "FC: Multimode, 50m (M5)");
if (map->page_00h[SFF8636_FC_TRANS_MEDIA_OFFSET] &
SFF8636_FC_TRANS_MEDIA_OM3)
- sprintf(value, "%s", "FC: Multimode, 50um (OM3)");
+ module_print_any_array_string_entry(pfx, "FC: Multimode, 50um (OM3)");
if (map->page_00h[SFF8636_FC_TRANS_MEDIA_OFFSET] &
SFF8636_FC_TRANS_MEDIA_SM)
- sprintf(value, "%s", "FC: Single Mode (SM)");
+ module_print_any_array_string_entry(pfx, "FC: Single Mode (SM)");
/* Fibre Channel speed */
if (map->page_00h[SFF8636_FC_SPEED_OFFSET] & SFF8636_FC_SPEED_1200_MBPS)
- sprintf(value, "%s", "FC: 1200 MBytes/sec");
+ module_print_any_array_string_entry(pfx, "FC: 1200 MBytes/sec");
if (map->page_00h[SFF8636_FC_SPEED_OFFSET] & SFF8636_FC_SPEED_800_MBPS)
- sprintf(value, "%s", "FC: 800 MBytes/sec");
+ module_print_any_array_string_entry(pfx, "FC: 800 MBytes/sec");
if (map->page_00h[SFF8636_FC_SPEED_OFFSET] & SFF8636_FC_SPEED_1600_MBPS)
- sprintf(value, "%s", "FC: 1600 MBytes/sec");
+ module_print_any_array_string_entry(pfx, "FC: 1600 MBytes/sec");
if (map->page_00h[SFF8636_FC_SPEED_OFFSET] & SFF8636_FC_SPEED_400_MBPS)
- sprintf(value, "%s", "FC: 400 MBytes/sec");
+ module_print_any_array_string_entry(pfx, "FC: 400 MBytes/sec");
if (map->page_00h[SFF8636_FC_SPEED_OFFSET] & SFF8636_FC_SPEED_3200_MBPS)
- sprintf(value, "%s", "FC: 3200 MBytes/sec");
+ module_print_any_array_string_entry(pfx, "FC: 3200 MBytes/sec");
if (map->page_00h[SFF8636_FC_SPEED_OFFSET] & SFF8636_FC_SPEED_200_MBPS)
- sprintf(value, "%s", "FC: 200 MBytes/sec");
+ module_print_any_array_string_entry(pfx, "FC: 200 MBytes/sec");
if (map->page_00h[SFF8636_FC_SPEED_OFFSET] & SFF8636_FC_SPEED_100_MBPS)
- sprintf(value, "%s", "FC: 100 MBytes/sec");
+ module_print_any_array_string_entry(pfx, "FC: 100 MBytes/sec");
- module_print_any_string(pfx, value);
+ if (is_json_context())
+ close_json_array("");
}
static void sff8636_show_encoding(const struct sff8636_memory_map *map)
--
2.53.0
^ permalink raw reply related
* [PATCH ethtool-next v3 2/3] sfpid: print all compliance codes
From: Aleksander Jan Bajkowski @ 2026-07-19 9:00 UTC (permalink / raw)
To: danieller, olek2, mkubecek, andrew, davem, edumazet, kuba, pabeni,
jbe, netdev
In-Reply-To: <20260719090458.659332-1-olek2@wp.pl>
SFP modules implement multiple compliance codes. This is common for
dual-rate modules. Before the `json` option was introduced, all
compliance codes were displayed. Currently, only the last code is
displayed. This commit fixes that bug. Compliance codes are
represented as array.
Before:
$ ethtool -m sfp-wan
...
Transceiver codes : 0x00 0x00 0x00 0x01 0x20 0x40 0x0c 0x15 0x00
Transceiver type : FC: 100 MBytes/sec
...
$ ethtool --json -m sfp-wan
[ {
...
"transceiver_codes": [ 0,0,0,1,32,64,12,21,0 ],
"transceiver_type": "FC: 100 MBytes/sec",
...
} ]
After:
$ ethtool -m sfp-wan
...
Transceiver codes : 0x00 0x00 0x00 0x01 0x20 0x40 0x0c 0x15 0x00
Transceiver type : Ethernet: 1000BASE-SX
Transceiver type : FC: intermediate distance (I)
Transceiver type : FC: Shortwave laser w/o OFC (SN)
Transceiver type : FC: Multimode, 62.5um (M6)
Transceiver type : FC: Multimode, 50um (M5)
Transceiver type : FC: 400 MBytes/sec
Transceiver type : FC: 200 MBytes/sec
Transceiver type : FC: 100 MBytes/sec
...
$ ethtool --json -m sfp-wan
[ {
...
"transceiver_codes": [ 0,0,0,1,32,64,12,21,0 ],
"transceiver_type": [ "Ethernet: 1000BASE-SX","FC: intermediate distance (I)","FC: Shortwave laser w/o OFC (SN)","FC: Multimode, 62.5um (M6)","FC: Multimode, 50um (M5)","FC: 400 MBytes/sec","FC: 200 MBytes/sec","FC: 100 MBytes/sec" ],
...
} ]
Fixes: 4071862f58d8 ("sfpid: Add JSON output handling to --module-info in SFF8079 modules")
Signed-off-by: Aleksander Jan Bajkowski <olek2@wp.pl>
---
Changes in v3:
- fix indentation and checkpatch warnings
Changes in v2:
- drop </pre> leftover
- use single sfp module in Before/After
- rename module_print_array_string() -> module_print_any_array_string_entry()
---
sfpid.c | 253 ++++++++++++++++++++++++++++----------------------------
1 file changed, 128 insertions(+), 125 deletions(-)
diff --git a/sfpid.c b/sfpid.c
index 5899f8c..065ac4c 100644
--- a/sfpid.c
+++ b/sfpid.c
@@ -50,7 +50,6 @@ static void sff8079_show_connector(const __u8 *id)
static void sff8079_show_transceiver(const __u8 *id)
{
static const char *pfx = "Transceiver type";
- char value[140] = "";
if (is_json_context()) {
open_json_array("transceiver_codes", "");
@@ -70,242 +69,246 @@ static void sff8079_show_transceiver(const __u8 *id)
"Transceiver codes", id[3], id[4], id[5], id[6],
id[7], id[8], id[9], id[10], id[36]);
}
+
+ if (is_json_context())
+ open_json_array("transceiver_type", "");
+
/* 10G Ethernet Compliance Codes */
if (id[3] & (1 << 7))
- sprintf(value, "%s",
- "10G Ethernet: 10G Base-ER [SFF-8472 rev10.4 onwards]");
+ module_print_any_array_string_entry(pfx,
+ "10G Ethernet: 10G Base-ER [SFF-8472 rev10.4 onwards]");
if (id[3] & (1 << 6))
- sprintf(value, "%s", "10G Ethernet: 10G Base-LRM");
+ module_print_any_array_string_entry(pfx, "10G Ethernet: 10G Base-LRM");
if (id[3] & (1 << 5))
- sprintf(value, "%s", "10G Ethernet: 10G Base-LR");
+ module_print_any_array_string_entry(pfx, "10G Ethernet: 10G Base-LR");
if (id[3] & (1 << 4))
- sprintf(value, "%s", "10G Ethernet: 10G Base-SR");
+ module_print_any_array_string_entry(pfx, "10G Ethernet: 10G Base-SR");
/* Infiniband Compliance Codes */
if (id[3] & (1 << 3))
- sprintf(value, "%s", "Infiniband: 1X SX");
+ module_print_any_array_string_entry(pfx, "Infiniband: 1X SX");
if (id[3] & (1 << 2))
- sprintf(value, "%s", "Infiniband: 1X LX");
+ module_print_any_array_string_entry(pfx, "Infiniband: 1X LX");
if (id[3] & (1 << 1))
- sprintf(value, "%s", "Infiniband: 1X Copper Active");
+ module_print_any_array_string_entry(pfx, "Infiniband: 1X Copper Active");
if (id[3] & (1 << 0))
- sprintf(value, "%s", "Infiniband: 1X Copper Passive");
+ module_print_any_array_string_entry(pfx, "Infiniband: 1X Copper Passive");
/* ESCON Compliance Codes */
if (id[4] & (1 << 7))
- sprintf(value, "%s", "ESCON: ESCON MMF, 1310nm LED");
+ module_print_any_array_string_entry(pfx, "ESCON: ESCON MMF, 1310nm LED");
if (id[4] & (1 << 6))
- sprintf(value, "%s", "ESCON: ESCON SMF, 1310nm Laser");
+ module_print_any_array_string_entry(pfx, "ESCON: ESCON SMF, 1310nm Laser");
/* SONET Compliance Codes */
if (id[4] & (1 << 5))
- sprintf(value, "%s", "SONET: OC-192, short reach");
+ module_print_any_array_string_entry(pfx, "SONET: OC-192, short reach");
if (id[4] & (1 << 4))
- sprintf(value, "%s", "SONET: SONET reach specifier bit 1");
+ module_print_any_array_string_entry(pfx, "SONET: SONET reach specifier bit 1");
if (id[4] & (1 << 3))
- sprintf(value, "%s", "SONET: SONET reach specifier bit 2");
+ module_print_any_array_string_entry(pfx, "SONET: SONET reach specifier bit 2");
if (id[4] & (1 << 2))
- sprintf(value, "%s", "SONET: OC-48, long reach");
+ module_print_any_array_string_entry(pfx, "SONET: OC-48, long reach");
if (id[4] & (1 << 1))
- sprintf(value, "%s", "SONET: OC-48, intermediate reach");
+ module_print_any_array_string_entry(pfx, "SONET: OC-48, intermediate reach");
if (id[4] & (1 << 0))
- sprintf(value, "%s", "SONET: OC-48, short reach");
+ module_print_any_array_string_entry(pfx, "SONET: OC-48, short reach");
if (id[5] & (1 << 6))
- sprintf(value, "%s", "SONET: OC-12, single mode, long reach");
+ module_print_any_array_string_entry(pfx, "SONET: OC-12, single mode, long reach");
if (id[5] & (1 << 5))
- sprintf(value, "%s", "SONET: OC-12, single mode, inter. reach");
+ module_print_any_array_string_entry(pfx, "SONET: OC-12, single mode, inter. reach");
if (id[5] & (1 << 4))
- sprintf(value, "%s", "SONET: OC-12, short reach");
+ module_print_any_array_string_entry(pfx, "SONET: OC-12, short reach");
if (id[5] & (1 << 2))
- sprintf(value, "%s", "SONET: OC-3, single mode, long reach");
+ module_print_any_array_string_entry(pfx, "SONET: OC-3, single mode, long reach");
if (id[5] & (1 << 1))
- sprintf(value, "%s", "SONET: OC-3, single mode, inter. reach");
+ module_print_any_array_string_entry(pfx, "SONET: OC-3, single mode, inter. reach");
if (id[5] & (1 << 0))
- sprintf(value, "%s", "SONET: OC-3, short reach");
+ module_print_any_array_string_entry(pfx, "SONET: OC-3, short reach");
/* Ethernet Compliance Codes */
if (id[6] & (1 << 7))
- sprintf(value, "%s", "Ethernet: BASE-PX");
+ module_print_any_array_string_entry(pfx, "Ethernet: BASE-PX");
if (id[6] & (1 << 6))
- sprintf(value, "%s", "Ethernet: BASE-BX10");
+ module_print_any_array_string_entry(pfx, "Ethernet: BASE-BX10");
if (id[6] & (1 << 5))
- sprintf(value, "%s", "Ethernet: 100BASE-FX");
+ module_print_any_array_string_entry(pfx, "Ethernet: 100BASE-FX");
if (id[6] & (1 << 4))
- sprintf(value, "%s", "Ethernet: 100BASE-LX/LX10");
+ module_print_any_array_string_entry(pfx, "Ethernet: 100BASE-LX/LX10");
if (id[6] & (1 << 3))
- sprintf(value, "%s", "Ethernet: 1000BASE-T");
+ module_print_any_array_string_entry(pfx, "Ethernet: 1000BASE-T");
if (id[6] & (1 << 2))
- sprintf(value, "%s", "Ethernet: 1000BASE-CX");
+ module_print_any_array_string_entry(pfx, "Ethernet: 1000BASE-CX");
if (id[6] & (1 << 1))
- sprintf(value, "%s", "Ethernet: 1000BASE-LX");
+ module_print_any_array_string_entry(pfx, "Ethernet: 1000BASE-LX");
if (id[6] & (1 << 0))
- sprintf(value, "%s", "Ethernet: 1000BASE-SX");
+ module_print_any_array_string_entry(pfx, "Ethernet: 1000BASE-SX");
/* Fibre Channel link length */
if (id[7] & (1 << 7))
- sprintf(value, "%s", "FC: very long distance (V)");
+ module_print_any_array_string_entry(pfx, "FC: very long distance (V)");
if (id[7] & (1 << 6))
- sprintf(value, "%s", "FC: short distance (S)");
+ module_print_any_array_string_entry(pfx, "FC: short distance (S)");
if (id[7] & (1 << 5))
- sprintf(value, "%s", "FC: intermediate distance (I)");
+ module_print_any_array_string_entry(pfx, "FC: intermediate distance (I)");
if (id[7] & (1 << 4))
- sprintf(value, "%s", "FC: long distance (L)");
+ module_print_any_array_string_entry(pfx, "FC: long distance (L)");
if (id[7] & (1 << 3))
- sprintf(value, "%s", "FC: medium distance (M)");
+ module_print_any_array_string_entry(pfx, "FC: medium distance (M)");
/* Fibre Channel transmitter technology */
if (id[7] & (1 << 2))
- sprintf(value, "%s", "FC: Shortwave laser, linear Rx (SA)");
+ module_print_any_array_string_entry(pfx, "FC: Shortwave laser, linear Rx (SA)");
if (id[7] & (1 << 1))
- sprintf(value, "%s", "FC: Longwave laser (LC)");
+ module_print_any_array_string_entry(pfx, "FC: Longwave laser (LC)");
if (id[7] & (1 << 0))
- sprintf(value, "%s", "FC: Electrical inter-enclosure (EL)");
+ module_print_any_array_string_entry(pfx, "FC: Electrical inter-enclosure (EL)");
if (id[8] & (1 << 7))
- sprintf(value, "%s", "FC: Electrical intra-enclosure (EL)");
+ module_print_any_array_string_entry(pfx, "FC: Electrical intra-enclosure (EL)");
if (id[8] & (1 << 6))
- sprintf(value, "%s", "FC: Shortwave laser w/o OFC (SN)");
+ module_print_any_array_string_entry(pfx, "FC: Shortwave laser w/o OFC (SN)");
if (id[8] & (1 << 5))
- sprintf(value, "%s", "FC: Shortwave laser with OFC (SL)");
+ module_print_any_array_string_entry(pfx, "FC: Shortwave laser with OFC (SL)");
if (id[8] & (1 << 4))
- sprintf(value, "%s", "FC: Longwave laser (LL)");
+ module_print_any_array_string_entry(pfx, "FC: Longwave laser (LL)");
if (id[8] & (1 << 3))
- sprintf(value, "%s", "Active Cable");
+ module_print_any_array_string_entry(pfx, "Active Cable");
if (id[8] & (1 << 2))
- sprintf(value, "%s", "Passive Cable");
+ module_print_any_array_string_entry(pfx, "Passive Cable");
if (id[8] & (1 << 1))
- sprintf(value, "%s", "FC: Copper FC-BaseT");
+ module_print_any_array_string_entry(pfx, "FC: Copper FC-BaseT");
/* Fibre Channel transmission media */
if (id[9] & (1 << 7))
- sprintf(value, "%s", "FC: Twin Axial Pair (TW)");
+ module_print_any_array_string_entry(pfx, "FC: Twin Axial Pair (TW)");
if (id[9] & (1 << 6))
- sprintf(value, "%s", "FC: Twisted Pair (TP)");
+ module_print_any_array_string_entry(pfx, "FC: Twisted Pair (TP)");
if (id[9] & (1 << 5))
- sprintf(value, "%s", "FC: Miniature Coax (MI)");
+ module_print_any_array_string_entry(pfx, "FC: Miniature Coax (MI)");
if (id[9] & (1 << 4))
- sprintf(value, "%s", "FC: Video Coax (TV)");
+ module_print_any_array_string_entry(pfx, "FC: Video Coax (TV)");
if (id[9] & (1 << 3))
- sprintf(value, "%s", "FC: Multimode, 62.5um (M6)");
+ module_print_any_array_string_entry(pfx, "FC: Multimode, 62.5um (M6)");
if (id[9] & (1 << 2))
- sprintf(value, "%s", "FC: Multimode, 50um (M5)");
+ module_print_any_array_string_entry(pfx, "FC: Multimode, 50um (M5)");
if (id[9] & (1 << 0))
- sprintf(value, "%s", "FC: Single Mode (SM)");
+ module_print_any_array_string_entry(pfx, "FC: Single Mode (SM)");
/* Fibre Channel speed */
if (id[10] & (1 << 7))
- sprintf(value, "%s", "FC: 1200 MBytes/sec");
+ module_print_any_array_string_entry(pfx, "FC: 1200 MBytes/sec");
if (id[10] & (1 << 6))
- sprintf(value, "%s", "FC: 800 MBytes/sec");
+ module_print_any_array_string_entry(pfx, "FC: 800 MBytes/sec");
if (id[10] & (1 << 5))
- sprintf(value, "%s", "FC: 1600 MBytes/sec");
+ module_print_any_array_string_entry(pfx, "FC: 1600 MBytes/sec");
if (id[10] & (1 << 4))
- sprintf(value, "%s", "FC: 400 MBytes/sec");
+ module_print_any_array_string_entry(pfx, "FC: 400 MBytes/sec");
if (id[10] & (1 << 3))
- sprintf(value, "%s", "FC: 3200 MBytes/sec");
+ module_print_any_array_string_entry(pfx, "FC: 3200 MBytes/sec");
if (id[10] & (1 << 2))
- sprintf(value, "%s", "FC: 200 MBytes/sec");
+ module_print_any_array_string_entry(pfx, "FC: 200 MBytes/sec");
if (id[10] & (1 << 0))
- sprintf(value, "%s", "FC: 100 MBytes/sec");
+ module_print_any_array_string_entry(pfx, "FC: 100 MBytes/sec");
/* Extended Specification Compliance Codes from SFF-8024 */
if (id[36] == 0x1)
- sprintf(value, "%s",
- "Extended: 100G AOC or 25GAUI C2M AOC with worst BER of 5x10^(-5)");
+ module_print_any_array_string_entry(pfx,
+ "Extended: 100G AOC or 25GAUI C2M AOC with worst BER of 5x10^(-5)");
if (id[36] == 0x2)
- sprintf(value, "%s", "Extended: 100G Base-SR4 or 25GBase-SR");
+ module_print_any_array_string_entry(pfx, "Extended: 100G Base-SR4 or 25GBase-SR");
if (id[36] == 0x3)
- sprintf(value, "%s", "Extended: 100G Base-LR4 or 25GBase-LR");
+ module_print_any_array_string_entry(pfx, "Extended: 100G Base-LR4 or 25GBase-LR");
if (id[36] == 0x4)
- sprintf(value, "%s", "Extended: 100G Base-ER4 or 25GBase-ER");
+ module_print_any_array_string_entry(pfx, "Extended: 100G Base-ER4 or 25GBase-ER");
if (id[36] == 0x8)
- sprintf(value, "%s",
- "Extended: 100G ACC or 25GAUI C2M ACC with worst BER of 5x10^(-5)");
+ module_print_any_array_string_entry(pfx,
+ "Extended: 100G ACC or 25GAUI C2M ACC with worst BER of 5x10^(-5)");
if (id[36] == 0xb)
- sprintf(value, "%s",
- "Extended: 100G Base-CR4 or 25G Base-CR CA-L");
+ module_print_any_array_string_entry(pfx,
+ "Extended: 100G Base-CR4 or 25G Base-CR CA-L");
if (id[36] == 0xc)
- sprintf(value, "%s", "Extended: 25G Base-CR CA-S");
+ module_print_any_array_string_entry(pfx, "Extended: 25G Base-CR CA-S");
if (id[36] == 0xd)
- sprintf(value, "%s", "Extended: 25G Base-CR CA-N");
+ module_print_any_array_string_entry(pfx, "Extended: 25G Base-CR CA-N");
if (id[36] == 0x16)
- sprintf(value, "%s",
- "Extended: 10Gbase-T with SFI electrical interface");
+ module_print_any_array_string_entry(pfx,
+ "Extended: 10Gbase-T with SFI electrical interface");
if (id[36] == 0x18)
- sprintf(value, "%s",
- "Extended: 100G AOC or 25GAUI C2M AOC with worst BER of 10^(-12)");
+ module_print_any_array_string_entry(pfx,
+ "Extended: 100G AOC or 25GAUI C2M AOC with worst BER of 10^(-12)");
if (id[36] == 0x19)
- sprintf(value, "%s",
- "Extended: 100G ACC or 25GAUI C2M ACC with worst BER of 10^(-12)");
+ module_print_any_array_string_entry(pfx,
+ "Extended: 100G ACC or 25GAUI C2M ACC with worst BER of 10^(-12)");
if (id[36] == 0x1a)
- sprintf(value, "%s",
- "Extended: 100GE-DWDM2 (DWDM transceiver using 2 wavelengths on a 1550 nm DWDM grid with a reach up to 80 km)");
+ module_print_any_array_string_entry(pfx,
+ "Extended: 100GE-DWDM2 (DWDM transceiver using 2 wavelengths on a 1550 nm DWDM grid with a reach up to 80 km)");
if (id[36] == 0x1b)
- sprintf(value, "%s",
- "Extended: 100G 1550nm WDM (4 wavelengths)");
+ module_print_any_array_string_entry(pfx,
+ "Extended: 100G 1550nm WDM (4 wavelengths)");
if (id[36] == 0x1c)
- sprintf(value, "%s", "Extended: 10Gbase-T Short Reach");
+ module_print_any_array_string_entry(pfx, "Extended: 10Gbase-T Short Reach");
if (id[36] == 0x1d)
- sprintf(value, "%s", "Extended: 5GBASE-T");
+ module_print_any_array_string_entry(pfx, "Extended: 5GBASE-T");
if (id[36] == 0x1e)
- sprintf(value, "%s", "Extended: 2.5GBASE-T");
+ module_print_any_array_string_entry(pfx, "Extended: 2.5GBASE-T");
if (id[36] == 0x1f)
- sprintf(value, "%s", "Extended: 40G SWDM4");
+ module_print_any_array_string_entry(pfx, "Extended: 40G SWDM4");
if (id[36] == 0x20)
- sprintf(value, "%s", "Extended: 100G SWDM4");
+ module_print_any_array_string_entry(pfx, "Extended: 100G SWDM4");
if (id[36] == 0x21)
- sprintf(value, "%s", "Extended: 100G PAM4 BiDi");
+ module_print_any_array_string_entry(pfx, "Extended: 100G PAM4 BiDi");
if (id[36] == 0x22)
- sprintf(value, "%s",
- "Extended: 4WDM-10 MSA (10km version of 100G CWDM4 with same RS(528,514) FEC in host system)");
+ module_print_any_array_string_entry(pfx,
+ "Extended: 4WDM-10 MSA (10km version of 100G CWDM4 with same RS(528,514) FEC in host system)");
if (id[36] == 0x23)
- sprintf(value, "%s",
- "Extended: 4WDM-20 MSA (20km version of 100GBASE-LR4 with RS(528,514) FEC in host system)");
+ module_print_any_array_string_entry(pfx,
+ "Extended: 4WDM-20 MSA (20km version of 100GBASE-LR4 with RS(528,514) FEC in host system)");
if (id[36] == 0x24)
- sprintf(value, "%s",
- "Extended: 4WDM-40 MSA (40km reach with APD receiver and RS(528,514) FEC in host system)");
+ module_print_any_array_string_entry(pfx,
+ "Extended: 4WDM-40 MSA (40km reach with APD receiver and RS(528,514) FEC in host system)");
if (id[36] == 0x25)
- sprintf(value, "%s",
- "Extended: 100GBASE-DR (clause 140), CAUI-4 (no FEC)");
+ module_print_any_array_string_entry(pfx,
+ "Extended: 100GBASE-DR (clause 140), CAUI-4 (no FEC)");
if (id[36] == 0x26)
- sprintf(value, "%s",
- "Extended: 100G-FR or 100GBASE-FR1 (clause 140), CAUI-4 (no FEC)");
+ module_print_any_array_string_entry(pfx,
+ "Extended: 100G-FR or 100GBASE-FR1 (clause 140), CAUI-4 (no FEC)");
if (id[36] == 0x27)
- sprintf(value, "%s",
- "Extended: 100G-LR or 100GBASE-LR1 (clause 140), CAUI-4 (no FEC)");
+ module_print_any_array_string_entry(pfx,
+ "Extended: 100G-LR or 100GBASE-LR1 (clause 140), CAUI-4 (no FEC)");
if (id[36] == 0x30)
- sprintf(value, "%s",
- "Extended: Active Copper Cable with 50GAUI, 100GAUI-2 or 200GAUI-4 C2M. Providing a worst BER of 10-6 or below");
+ module_print_any_array_string_entry(pfx,
+ "Extended: Active Copper Cable with 50GAUI, 100GAUI-2 or 200GAUI-4 C2M. Providing a worst BER of 10-6 or below");
if (id[36] == 0x31)
- sprintf(value, "%s",
- "Extended: Active Optical Cable with 50GAUI, 100GAUI-2 or 200GAUI-4 C2M. Providing a worst BER of 10-6 or below");
+ module_print_any_array_string_entry(pfx,
+ "Extended: Active Optical Cable with 50GAUI, 100GAUI-2 or 200GAUI-4 C2M. Providing a worst BER of 10-6 or below");
if (id[36] == 0x32)
- sprintf(value, "%s",
- "Extended: Active Copper Cable with 50GAUI, 100GAUI-2 or 200GAUI-4 C2M. Providing a worst BER of 2.6x10-4 for ACC, 10-5 for AUI, or below");
+ module_print_any_array_string_entry(pfx,
+ "Extended: Active Copper Cable with 50GAUI, 100GAUI-2 or 200GAUI-4 C2M. Providing a worst BER of 2.6x10-4 for ACC, 10-5 for AUI, or below");
if (id[36] == 0x33)
- sprintf(value, "%s",
- "Extended: Active Optical Cable with 50GAUI, 100GAUI-2 or 200GAUI-4 C2M. Providing a worst BER of 2.6x10-4 for ACC, 10-5 for AUI, or below");
+ module_print_any_array_string_entry(pfx,
+ "Extended: Active Optical Cable with 50GAUI, 100GAUI-2 or 200GAUI-4 C2M. Providing a worst BER of 2.6x10-4 for ACC, 10-5 for AUI, or below");
if (id[36] == 0x40)
- sprintf(value, "%s",
- "Extended: 50GBASE-CR, 100GBASE-CR2, or 200GBASE-CR4");
+ module_print_any_array_string_entry(pfx,
+ "Extended: 50GBASE-CR, 100GBASE-CR2, or 200GBASE-CR4");
if (id[36] == 0x41)
- sprintf(value, "%s",
- "Extended: 50GBASE-SR, 100GBASE-SR2, or 200GBASE-SR4");
+ module_print_any_array_string_entry(pfx,
+ "Extended: 50GBASE-SR, 100GBASE-SR2, or 200GBASE-SR4");
if (id[36] == 0x42)
- sprintf(value, "%s", "Extended: 50GBASE-FR or 200GBASE-DR4");
+ module_print_any_array_string_entry(pfx, "Extended: 50GBASE-FR or 200GBASE-DR4");
if (id[36] == 0x43)
- sprintf(value, "%s", "Extended: 200GBASE-FR4");
+ module_print_any_array_string_entry(pfx, "Extended: 200GBASE-FR4");
if (id[36] == 0x44)
- sprintf(value, "%s", "Extended: 200G 1550 nm PSM4");
+ module_print_any_array_string_entry(pfx, "Extended: 200G 1550 nm PSM4");
if (id[36] == 0x45)
- sprintf(value, "%s", "Extended: 50GBASE-LR");
+ module_print_any_array_string_entry(pfx, "Extended: 50GBASE-LR");
if (id[36] == 0x46)
- sprintf(value, "%s", "Extended: 200GBASE-LR4");
+ module_print_any_array_string_entry(pfx, "Extended: 200GBASE-LR4");
if (id[36] == 0x50)
- sprintf(value, "%s", "Extended: 64GFC EA");
+ module_print_any_array_string_entry(pfx, "Extended: 64GFC EA");
if (id[36] == 0x51)
- sprintf(value, "%s", "Extended: 64GFC SW");
+ module_print_any_array_string_entry(pfx, "Extended: 64GFC SW");
if (id[36] == 0x52)
- sprintf(value, "%s", "Extended: 64GFC LW");
+ module_print_any_array_string_entry(pfx, "Extended: 64GFC LW");
if (id[36] == 0x53)
- sprintf(value, "%s", "Extended: 128GFC EA");
+ module_print_any_array_string_entry(pfx, "Extended: 128GFC EA");
if (id[36] == 0x54)
- sprintf(value, "%s", "Extended: 128GFC SW");
+ module_print_any_array_string_entry(pfx, "Extended: 128GFC SW");
if (id[36] == 0x55)
- sprintf(value, "%s", "Extended: 128GFC LW");
+ module_print_any_array_string_entry(pfx, "Extended: 128GFC LW");
- if (value[0] != '\0')
- module_print_any_string(pfx, value);
+ if (is_json_context())
+ close_json_array("");
}
static void sff8079_show_encoding(const __u8 *id)
--
2.53.0
^ permalink raw reply related
* [PATCH ethtool-next v3 1/3] sfpid: print all implemented options
From: Aleksander Jan Bajkowski @ 2026-07-19 9:00 UTC (permalink / raw)
To: danieller, olek2, mkubecek, andrew, davem, edumazet, kuba, pabeni,
jbe, netdev
SFP modules implement multiple options. Before the “json” option was
introduced, all options were listed. Currently, only the last option
is listed. This commit fixes this bug. Options are represented as array.
Before:
$ ethtool -m sfp-wan
...
Option values : 0x00 0x32
Option : RATE_SELECT implemented
...
$ ethtool --json -m sfp-wan
[ {
...
"option_values": [ 0,50 ],
"option": "RATE_SELECT implemented",
...
} ]
After:
$ ethtool -m sfp-wan
...
Option values : 0x00 0x32
Option : RX_LOS implemented
Option : TX_DISABLE implemented
Option : RATE_SELECT implemented
...
$ ethtool --json -m sfp-wan
[ {
...
"option_values": [ 0,50 ],
"option": [ "RX_LOS implemented","TX_DISABLE implemented","RATE_SELECT implemented" ],
...
} ]
Fixes: 4071862f58d8 ("sfpid: Add JSON output handling to --module-info in SFF8079 modules")
Signed-off-by: Aleksander Jan Bajkowski <olek2@wp.pl>
---
Changes in v3:
- fix indentation and checkpatch warnings
Changes in v2:
- fix typo introduced -> introduced
- rename module_print_array_string() -> module_print_any_array_string_entry()
---
module-common.c | 8 ++++++++
module-common.h | 1 +
sfpid.c | 35 +++++++++++++++++++----------------
3 files changed, 28 insertions(+), 16 deletions(-)
diff --git a/module-common.c b/module-common.c
index 42fccf6..43ff649 100644
--- a/module-common.c
+++ b/module-common.c
@@ -258,6 +258,14 @@ void module_print_any_bool(const char *fn, char *given_json_fn, bool value,
printf("\t%-41s : %s\n", fn, str_value);
}
+void module_print_any_array_string_entry(const char *fn, const char *value)
+{
+ if (is_json_context())
+ print_string(PRINT_JSON, NULL, "%s", value);
+ else
+ printf("\t%-41s : %s\n", fn, value);
+}
+
void module_show_value_with_unit(const __u8 *id, unsigned int reg,
const char *name, unsigned int mult,
const char *unit)
diff --git a/module-common.h b/module-common.h
index 4063448..f3baf2a 100644
--- a/module-common.h
+++ b/module-common.h
@@ -281,6 +281,7 @@ void module_print_any_string(const char *fn, const char *value);
void module_print_any_float(const char *fn, float value, const char *unit);
void module_print_any_bool(const char *fn, char *given_json_fn, bool value,
const char *str_value);
+void module_print_any_array_string_entry(const char *fn, const char *value);
void module_show_value_with_unit(const __u8 *id, unsigned int reg,
const char *name, unsigned int mult,
const char *unit);
diff --git a/sfpid.c b/sfpid.c
index 74a6f51..5899f8c 100644
--- a/sfpid.c
+++ b/sfpid.c
@@ -396,7 +396,6 @@ static void sff8079_show_wavelength_or_copper_compliance(const __u8 *id)
static void sff8079_show_options(const __u8 *id)
{
static const char *pfx = "Option";
- char value[64] = "";
if (is_json_context()) {
open_json_array("option_values", "");
@@ -407,35 +406,39 @@ static void sff8079_show_options(const __u8 *id)
printf("\t%-41s : 0x%02x 0x%02x\n", "Option values", id[64],
id[65]);
}
+
+ if (is_json_context())
+ open_json_array("option", "");
+
if (id[65] & (1 << 1))
- sprintf(value, "%s", "RX_LOS implemented");
+ module_print_any_array_string_entry(pfx, "RX_LOS implemented");
if (id[65] & (1 << 2))
- sprintf(value, "%s", "RX_LOS implemented, inverted");
+ module_print_any_array_string_entry(pfx, "RX_LOS implemented, inverted");
if (id[65] & (1 << 3))
- sprintf(value, "%s", "TX_FAULT implemented");
+ module_print_any_array_string_entry(pfx, "TX_FAULT implemented");
if (id[65] & (1 << 4))
- sprintf(value, "%s", "TX_DISABLE implemented");
+ module_print_any_array_string_entry(pfx, "TX_DISABLE implemented");
if (id[65] & (1 << 5))
- sprintf(value, "%s", "RATE_SELECT implemented");
+ module_print_any_array_string_entry(pfx, "RATE_SELECT implemented");
if (id[65] & (1 << 6))
- sprintf(value, "%s", "Tunable transmitter technology");
+ module_print_any_array_string_entry(pfx, "Tunable transmitter technology");
if (id[65] & (1 << 7))
- sprintf(value, "%s", "Receiver decision threshold implemented");
+ module_print_any_array_string_entry(pfx, "Receiver decision threshold implemented");
if (id[64] & (1 << 0))
- sprintf(value, "%s", "Linear receiver output implemented");
+ module_print_any_array_string_entry(pfx, "Linear receiver output implemented");
if (id[64] & (1 << 1))
- sprintf(value, "%s", "Power level 2 requirement");
+ module_print_any_array_string_entry(pfx, "Power level 2 requirement");
if (id[64] & (1 << 2))
- sprintf(value, "%s", "Cooled transceiver implemented");
+ module_print_any_array_string_entry(pfx, "Cooled transceiver implemented");
if (id[64] & (1 << 3))
- sprintf(value, "%s", "Retimer or CDR implemented");
+ module_print_any_array_string_entry(pfx, "Retimer or CDR implemented");
if (id[64] & (1 << 4))
- sprintf(value, "%s", "Paging implemented");
+ module_print_any_array_string_entry(pfx, "Paging implemented");
if (id[64] & (1 << 5))
- sprintf(value, "%s", "Power level 3 requirement");
+ module_print_any_array_string_entry(pfx, "Power level 3 requirement");
- if (value[0] != '\0')
- module_print_any_string(pfx, value);
+ if (is_json_context())
+ close_json_array("");
}
static void sff8079_show_all_common(const __u8 *id)
--
2.53.0
^ permalink raw reply related
* tc/iproute2 7.1 crash with kernel with and without SECCOMP
From: Jorge Bastos @ 2026-07-19 9:03 UTC (permalink / raw)
To: netdev
Howdy,
I have some VM's that were working ok, and since arround kernel 6.18x
and 7.1x kernel crashes when using fireqos, that uses "tc" from
iproute2,
I'm using now kernels with SECCOMP because of ssh 10.4+,
How to make this compatible and working?
Thanks in advanced,
Jorge Bastos
BUG: kernel NULL pointer dereference, address: 0000000000000628
#PF: supervisor write access in kernel mode
#PF: error_code(0x0002) - not-present page
PGD 0 P4D 0
Oops: Oops: 0002 [#1] SMP PTI
CPU: 1 UID: 0 PID: 14042 Comm: tc Not tainted 7.1.3 #1 PREEMPT(full)
Hardware name: Microsoft Corporation Virtual Machine/Virtual Machine,
BIOS 090007 05/18/2018
RIP: 0010:tcx_miniq_inc+0x2c/0x40 [sch_ingress]
Code: 89 fb e8 f7 77 52 e1 85 c0 75 18 48 8d 3d 1c 3d e7 ff ba 81 00 00
00 48 c7 c6 23 70 0d a0 67 48 0f b9 3a 48 8b 83 00 02 00 00 <ff> 80 28
06 00 00 5b c3 cc cc cc cc 0f 1f 84 00 00 00 00 00 90 90
RSP: 0018:ffffc9000064f930 EFLAGS: 00010202
RAX: 0000000000000000 RBX: ffff88805783b808 RCX: 0000000000000000
RDX: ffff88805783b800 RSI: 0000000000000082 RDI: ffffffff82465bc0
RBP: 00000000fffffff4 R08: ffff888004cc6e88 R09: 00000000000000d4
R10: 00000000000000d0 R11: 00000000000000d5 R12: ffff88805783b808
R13: ffff88808c92efa8 R14: ffff888004651000 R15: ffffc9000064fb48
FS: 00007fb985608780(0000) GS:ffff888180ab3000(0000)
knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 0000000000000628 CR3: 00000000a2b34004 CR4: 00000000001706b0
Call Trace:
<TASK>
ingress_init+0x6e/0x110 [sch_ingress]
qdisc_create+0x297/0x370
tc_modify_qdisc+0x639/0x6c0
rtnetlink_rcv_msg+0x278/0x2d0
? __kmalloc_node_track_caller_noprof+0x37/0x320
? __alloc_skb+0xc3/0x110
? __pfx_rtnetlink_rcv_msg+0x10/0x10
netlink_rcv_skb+0x83/0xe0
netlink_unicast+0x11c/0x1b0
netlink_sendmsg+0x271/0x2d0
sock_sendmsg_nosec+0x32/0x40
____sys_sendmsg+0x10b/0x180
? copy_msghdr_from_user+0x6a/0xa0
___sys_sendmsg+0x79/0xc0
? ___sys_recvmsg+0x82/0xb0
? __handle_mm_fault+0x71f/0x7a0
? css_uses_rstat+0x9/0x20
? __css_rstat_updated+0x22/0x70
__sys_sendmsg+0x63/0xa0
do_syscall_64+0x116/0x2a0
entry_SYSCALL_64_after_hwframe+0x76/0x7e
RIP: 0033:0x7fb98529bcb2
Code: 18 41 8b 93 08 03 00 00 59 5e 48 83 f8 fc 75 1a 83 e2 39 83 fa 08
75 12 e8 2b ff ff ff 0f 1f 00 49 89 ca 48 8b 44 24 20 0f 05 <48> 83 c4
18 c3 66 0f 1f 84 00 00 00 00 00 48 83 ec 10 ff 74 24 18
RSP: 002b:00007ffd5106e180 EFLAGS: 00000202 ORIG_RAX: 000000000000002e
RAX: ffffffffffffffda RBX: 000055bdbf033640 RCX: 00007fb98529bcb2
RDX: 0000000000000000 RSI: 00007ffd5106e240 RDI: 0000000000000003
RBP: 0000000000000000 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000202 R12: 0000000000000000
R13: 000000006a57f9a8 R14: 00007ffd5107e540 R15: 000055bdbf033640
</TASK>
Modules linked in: sch_ingress sch_htb ifb xt_tcpmss iptable_mangle
xt_tcpudp xt_conntrack iptable_nat nf_nat nf_conntrack nf_defrag_ipv6
nf_defrag_ipv4 iptable_filter ip_tables x_tables aesni_intel gf128mul
libaes aead crypto_skcipher rtc_cmos button sg sch_fq_codel loop fuse
configfs
CR2: 0000000000000628
---[ end trace 0000000000000000 ]---
RIP: 0010:tcx_miniq_inc+0x2c/0x40 [sch_ingress]
Code: 89 fb e8 f7 77 52 e1 85 c0 75 18 48 8d 3d 1c 3d e7 ff ba 81 00 00
00 48 c7 c6 23 70 0d a0 67 48 0f b9 3a 48 8b 83 00 02 00 00 <ff> 80 28
06 00 00 5b c3 cc cc cc cc 0f 1f 84 00 00 00 00 00 90 90
RSP: 0018:ffffc9000064f930 EFLAGS: 00010202
RAX: 0000000000000000 RBX: ffff88805783b808 RCX: 0000000000000000
RDX: ffff88805783b800 RSI: 0000000000000082 RDI: ffffffff82465bc0
RBP: 00000000fffffff4 R08: ffff888004cc6e88 R09: 00000000000000d4
R10: 00000000000000d0 R11: 00000000000000d5 R12: ffff88805783b808
R13: ffff88808c92efa8 R14: ffff888004651000 R15: ffffc9000064fb48
FS: 00007fb985608780(0000) GS:ffff888180ab3000(0000)
knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 0000000000000628 CR3: 00000000a2b34004 CR4: 00000000001706b0
^ permalink raw reply
* [PATCH net-next v12 09/12] enic: add MBOX VF handlers for capability, register and link state
From: Satish Kharat @ 2026-07-19 8:41 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni
Cc: netdev, linux-kernel, Breno Leitao, Sesidhar Baddela,
Satish Kharat
In-Reply-To: <20260719-enic-sriov-v2-admin-channel-v2-v12-0-6ff2c617001d@cisco.com>
Implement VF-side mailbox message processing for SR-IOV V2
admin channel communication.
VF receive handlers:
- VF_CAPABILITY_REPLY: store PF protocol version, signal
completion
- VF_REGISTER_REPLY: mark VF as registered, signal completion
- VF_UNREGISTER_REPLY: mark VF as unregistered, signal
completion
- PF_LINK_STATE_NOTIF: update carrier state via
netif_carrier_on/off, send ACK back to PF
VF initiation functions for the probe-time handshake:
- enic_mbox_vf_capability_check: send capability request,
wait for PF reply via completion
- enic_mbox_vf_register: send register request, wait for
PF confirmation via completion
- enic_mbox_vf_unregister: send unregister request, wait
for PF confirmation
The wait helper (enic_mbox_wait_reply) uses
wait_for_completion_timeout, signaled when the admin ISR and
CQ-poll/dispatch workqueue pipeline delivers the reply message.
Signed-off-by: Satish Kharat <satishkh@cisco.com>
---
drivers/net/ethernet/cisco/enic/enic.h | 11 ++
drivers/net/ethernet/cisco/enic/enic_mbox.c | 277 +++++++++++++++++++++++++++-
drivers/net/ethernet/cisco/enic/enic_mbox.h | 3 +
3 files changed, 290 insertions(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/cisco/enic/enic.h b/drivers/net/ethernet/cisco/enic/enic.h
index d459318c46fc..a9a376d2cf0e 100644
--- a/drivers/net/ethernet/cisco/enic/enic.h
+++ b/drivers/net/ethernet/cisco/enic/enic.h
@@ -258,6 +258,8 @@ struct enic {
u32 tx_coalesce_usecs;
u16 num_vfs;
enum enic_vf_type vf_type;
+ bool vf_registered;
+ u32 pf_cap_version;
unsigned int enable_count;
spinlock_t enic_api_lock;
bool enic_api_busy;
@@ -313,6 +315,15 @@ struct enic {
/* MBOX protocol state — mbox_lock serializes admin WQ sends */
struct mutex mbox_lock;
u64 mbox_msg_num;
+ /* MBOX request-reply state. Written by the process-context request
+ * helpers (capability/register/unregister) and read/cleared by the
+ * admin_msg_work receive handlers. No explicit lock is needed because
+ * only one request is in flight at a time: requesters run under RTNL or
+ * single-threaded probe/remove, so each request is serialized and its
+ * reply completes mbox_comp before the next request is issued.
+ */
+ struct completion mbox_comp;
+ u8 mbox_expected_reply;
/* PF: per-VF MBOX state, allocated when SRIOV V2 is enabled */
struct enic_vf_state {
diff --git a/drivers/net/ethernet/cisco/enic/enic_mbox.c b/drivers/net/ethernet/cisco/enic/enic_mbox.c
index b6f05b03ae26..701972f90627 100644
--- a/drivers/net/ethernet/cisco/enic/enic_mbox.c
+++ b/drivers/net/ethernet/cisco/enic/enic_mbox.c
@@ -5,6 +5,7 @@
#include <linux/netdevice.h>
#include <linux/dma-mapping.h>
#include <linux/delay.h>
+#include <linux/completion.h>
#include "vnic_dev.h"
#include "vnic_wq.h"
@@ -135,6 +136,16 @@ int enic_mbox_send_msg(struct enic *enic, u8 msg_type, u16 dst_vnic_id,
return err;
}
+static int enic_mbox_wait_reply(struct enic *enic, unsigned long timeout_ms)
+{
+ unsigned long left;
+
+ left = wait_for_completion_timeout(&enic->mbox_comp,
+ msecs_to_jiffies(timeout_ms));
+
+ return left ? 0 : -ETIMEDOUT;
+}
+
int enic_mbox_send_link_state(struct enic *enic, u16 vf_id, u32 link_state)
{
struct enic_mbox_pf_link_state_notif_msg notif = {};
@@ -306,6 +317,166 @@ static void enic_mbox_pf_process_msg(struct enic *enic,
hdr->msg_type, vf_id, err);
}
+static void enic_mbox_vf_handle_capability_reply(struct enic *enic,
+ void *payload)
+{
+ struct enic_mbox_vf_capability_reply_msg *reply = payload;
+
+ if (enic->mbox_expected_reply != ENIC_MBOX_VF_CAPABILITY_REPLY) {
+ netdev_warn(enic->netdev,
+ "MBOX: stale capability reply (expected %u), drop\n",
+ enic->mbox_expected_reply);
+ return;
+ }
+
+ if (le16_to_cpu(reply->reply.ret_major) == 0)
+ enic->pf_cap_version = le32_to_cpu(reply->version);
+ else
+ netdev_warn(enic->netdev,
+ "MBOX: PF rejected capability request: %u/%u\n",
+ le16_to_cpu(reply->reply.ret_major),
+ le16_to_cpu(reply->reply.ret_minor));
+ complete(&enic->mbox_comp);
+}
+
+static void enic_mbox_vf_handle_register_reply(struct enic *enic,
+ void *payload)
+{
+ struct enic_mbox_vf_register_reply_msg *reply = payload;
+
+ if (enic->mbox_expected_reply != ENIC_MBOX_VF_REGISTER_REPLY) {
+ netdev_warn(enic->netdev,
+ "MBOX: stale register reply (expected %u), drop\n",
+ enic->mbox_expected_reply);
+ return;
+ }
+
+ if (le16_to_cpu(reply->reply.ret_major)) {
+ netdev_warn(enic->netdev,
+ "MBOX: VF register rejected by PF: %u/%u\n",
+ le16_to_cpu(reply->reply.ret_major),
+ le16_to_cpu(reply->reply.ret_minor));
+ } else {
+ enic->vf_registered = true;
+ }
+ complete(&enic->mbox_comp);
+}
+
+static void enic_mbox_vf_handle_unregister_reply(struct enic *enic,
+ void *payload)
+{
+ struct enic_mbox_vf_register_reply_msg *reply = payload;
+
+ if (enic->mbox_expected_reply != ENIC_MBOX_VF_UNREGISTER_REPLY) {
+ netdev_warn(enic->netdev,
+ "MBOX: stale unregister reply (expected %u), drop\n",
+ enic->mbox_expected_reply);
+ return;
+ }
+
+ if (le16_to_cpu(reply->reply.ret_major)) {
+ netdev_warn(enic->netdev,
+ "MBOX: VF unregister rejected by PF: %u/%u\n",
+ le16_to_cpu(reply->reply.ret_major),
+ le16_to_cpu(reply->reply.ret_minor));
+ } else {
+ enic->vf_registered = false;
+ }
+ complete(&enic->mbox_comp);
+}
+
+static void enic_mbox_vf_handle_link_state(struct enic *enic, void *payload)
+{
+ struct enic_mbox_pf_link_state_notif_msg *notif = payload;
+ struct enic_mbox_pf_link_state_ack_msg ack = {};
+ int err;
+
+ switch (le32_to_cpu(notif->link_state)) {
+ case ENIC_MBOX_LINK_STATE_ENABLE:
+ if (!netif_carrier_ok(enic->netdev))
+ netif_carrier_on(enic->netdev);
+ netdev_dbg(enic->netdev, "MBOX: link state -> UP\n");
+ break;
+ case ENIC_MBOX_LINK_STATE_DISABLE:
+ if (netif_carrier_ok(enic->netdev))
+ netif_carrier_off(enic->netdev);
+ netdev_dbg(enic->netdev, "MBOX: link state -> DOWN\n");
+ break;
+ default:
+ netdev_warn(enic->netdev, "MBOX: unknown link state %u\n",
+ le32_to_cpu(notif->link_state));
+ ack.ack.ret_major = cpu_to_le16(ENIC_MBOX_ERR_GENERIC);
+ break;
+ }
+
+ err = enic_mbox_send_msg(enic, ENIC_MBOX_PF_LINK_STATE_ACK,
+ ENIC_MBOX_DST_PF, &ack, sizeof(ack));
+ if (err && net_ratelimit())
+ netdev_warn(enic->netdev,
+ "MBOX: failed to send link state ACK: %d\n", err);
+}
+
+static bool enic_mbox_vf_payload_ok(struct enic *enic, u8 msg_type,
+ u16 payload_len, size_t min_len)
+{
+ if (payload_len < min_len) {
+ netdev_warn(enic->netdev,
+ "MBOX: short payload for type %u (%u < %zu)\n",
+ msg_type, payload_len, min_len);
+ return false;
+ }
+ return true;
+}
+
+static void enic_mbox_vf_process_msg(struct enic *enic,
+ struct enic_mbox_hdr *hdr, void *payload,
+ u16 payload_len)
+{
+ switch (hdr->msg_type) {
+ case ENIC_MBOX_VF_CAPABILITY_REPLY: {
+ size_t exp = sizeof(struct enic_mbox_vf_capability_reply_msg);
+
+ if (!enic_mbox_vf_payload_ok(enic, hdr->msg_type,
+ payload_len, exp))
+ return;
+ enic_mbox_vf_handle_capability_reply(enic, payload);
+ break;
+ }
+ case ENIC_MBOX_VF_REGISTER_REPLY: {
+ size_t exp = sizeof(struct enic_mbox_vf_register_reply_msg);
+
+ if (!enic_mbox_vf_payload_ok(enic, hdr->msg_type,
+ payload_len, exp))
+ return;
+ enic_mbox_vf_handle_register_reply(enic, payload);
+ break;
+ }
+ case ENIC_MBOX_VF_UNREGISTER_REPLY: {
+ size_t exp = sizeof(struct enic_mbox_vf_register_reply_msg);
+
+ if (!enic_mbox_vf_payload_ok(enic, hdr->msg_type,
+ payload_len, exp))
+ return;
+ enic_mbox_vf_handle_unregister_reply(enic, payload);
+ break;
+ }
+ case ENIC_MBOX_PF_LINK_STATE_NOTIF: {
+ size_t exp = sizeof(struct enic_mbox_pf_link_state_notif_msg);
+
+ if (!enic_mbox_vf_payload_ok(enic, hdr->msg_type,
+ payload_len, exp))
+ return;
+ enic_mbox_vf_handle_link_state(enic, payload);
+ break;
+ }
+ default:
+ netdev_dbg(enic->netdev,
+ "MBOX: VF unhandled msg type %u\n",
+ hdr->msg_type);
+ break;
+ }
+}
+
static void enic_mbox_recv_handler(struct enic *enic, void *buf,
unsigned int len)
{
@@ -344,13 +515,117 @@ static void enic_mbox_recv_handler(struct enic *enic, void *buf,
payload = buf + sizeof(*hdr);
- if (enic->vf_state)
+ if (enic->vf_state) {
enic_mbox_pf_process_msg(enic, hdr, payload);
+ } else if (le16_to_cpu(hdr->src_vnic_id) == ENIC_MBOX_DST_PF) {
+ /* src_vnic_id was overwritten from the hardware-verified CQ
+ * VLAN sender field, so a VF only accepts messages that the
+ * adapter attributes to the PF. Its sole admin-channel peer is
+ * the PF; drop anything else as a spoofed notification.
+ */
+ enic_mbox_vf_process_msg(enic, hdr, payload,
+ msg_len - (u16)sizeof(*hdr));
+ } else if (net_ratelimit()) {
+ netdev_warn(enic->netdev,
+ "MBOX: VF dropping non-PF message from vnic %u\n",
+ le16_to_cpu(hdr->src_vnic_id));
+ }
+}
+
+int enic_mbox_vf_capability_check(struct enic *enic)
+{
+ struct enic_mbox_vf_capability_msg req = {};
+ int err;
+
+ enic->pf_cap_version = 0;
+ reinit_completion(&enic->mbox_comp);
+ enic->mbox_expected_reply = ENIC_MBOX_VF_CAPABILITY_REPLY;
+ req.version = cpu_to_le32(ENIC_MBOX_CAP_VERSION_1);
+
+ err = enic_mbox_send_msg(enic, ENIC_MBOX_VF_CAPABILITY_REQUEST,
+ ENIC_MBOX_DST_PF, &req, sizeof(req));
+ if (err) {
+ enic->mbox_expected_reply = 0;
+ return err;
+ }
+
+ err = enic_mbox_wait_reply(enic, 3000);
+ enic->mbox_expected_reply = 0;
+ if (err) {
+ netdev_warn(enic->netdev,
+ "MBOX: no capability reply from PF\n");
+ return err;
+ }
+
+ if (enic->pf_cap_version < ENIC_MBOX_CAP_VERSION_1) {
+ netdev_warn(enic->netdev,
+ "MBOX: PF rejected capability request or reported unsupported version %u\n",
+ enic->pf_cap_version);
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+int enic_mbox_vf_register(struct enic *enic)
+{
+ int err;
+
+ enic->vf_registered = false;
+ reinit_completion(&enic->mbox_comp);
+ enic->mbox_expected_reply = ENIC_MBOX_VF_REGISTER_REPLY;
+
+ err = enic_mbox_send_msg(enic, ENIC_MBOX_VF_REGISTER_REQUEST,
+ ENIC_MBOX_DST_PF, NULL, 0);
+ if (err) {
+ enic->mbox_expected_reply = 0;
+ return err;
+ }
+
+ err = enic_mbox_wait_reply(enic, 3000);
+ enic->mbox_expected_reply = 0;
+ if (err) {
+ netdev_warn(enic->netdev,
+ "MBOX: VF registration with PF timed out\n");
+ return err;
+ }
+
+ if (!enic->vf_registered)
+ return -ENODEV;
+
+ return 0;
+}
+
+int enic_mbox_vf_unregister(struct enic *enic)
+{
+ int err;
+
+ if (!enic->vf_registered)
+ return 0;
+
+ reinit_completion(&enic->mbox_comp);
+ enic->mbox_expected_reply = ENIC_MBOX_VF_UNREGISTER_REPLY;
+
+ err = enic_mbox_send_msg(enic, ENIC_MBOX_VF_UNREGISTER_REQUEST,
+ ENIC_MBOX_DST_PF, NULL, 0);
+ if (err) {
+ enic->mbox_expected_reply = 0;
+ return err;
+ }
+
+ err = enic_mbox_wait_reply(enic, 3000);
+ enic->mbox_expected_reply = 0;
+ if (err)
+ return err;
+ if (enic->vf_registered)
+ return -EACCES;
+ return 0;
}
void enic_mbox_init(struct enic *enic)
{
enic->mbox_msg_num = 0;
mutex_init(&enic->mbox_lock);
+ init_completion(&enic->mbox_comp);
enic->admin_rq_handler = enic_mbox_recv_handler;
}
diff --git a/drivers/net/ethernet/cisco/enic/enic_mbox.h b/drivers/net/ethernet/cisco/enic/enic_mbox.h
index f1de67db1273..15e30ee2b0ed 100644
--- a/drivers/net/ethernet/cisco/enic/enic_mbox.h
+++ b/drivers/net/ethernet/cisco/enic/enic_mbox.h
@@ -88,5 +88,8 @@ void enic_mbox_init(struct enic *enic);
int enic_mbox_send_msg(struct enic *enic, u8 msg_type, u16 dst_vnic_id,
void *payload, u16 payload_len);
int enic_mbox_send_link_state(struct enic *enic, u16 vf_id, u32 link_state);
+int enic_mbox_vf_capability_check(struct enic *enic);
+int enic_mbox_vf_register(struct enic *enic);
+int enic_mbox_vf_unregister(struct enic *enic);
#endif /* _ENIC_MBOX_H_ */
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v12 08/12] enic: add MBOX PF handlers for VF register and capability
From: Satish Kharat @ 2026-07-19 8:41 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni
Cc: netdev, linux-kernel, Breno Leitao, Sesidhar Baddela,
Satish Kharat
In-Reply-To: <20260719-enic-sriov-v2-admin-channel-v2-v12-0-6ff2c617001d@cisco.com>
Implement PF-side mailbox message processing for SR-IOV V2
admin channel communication.
When the PF receives messages from VFs, the dispatch routes
them to type-specific handlers:
- VF_CAPABILITY_REQUEST: reply with protocol version 1
- VF_REGISTER_REQUEST: send the register reply, mark the
VF registered on success, then send PF_LINK_STATE_NOTIF
reflecting the PF's current carrier state
- VF_UNREGISTER_REQUEST: mark VF unregistered, send reply
- PF_LINK_STATE_ACK: log errors from VF acknowledgment
Per-VF state (struct enic_vf_state) is tracked via enic->vf_state
which will be allocated when SRIOV V2 is enabled.
Remove the CONFIG_PCI_IOV guard from num_vfs in struct enic. The
PF handlers reference enic->num_vfs for VF ID bounds checking in
enic_mbox.c, which is compiled unconditionally. The field must be
visible regardless of CONFIG_PCI_IOV to avoid build failures.
Add enic_mbox_send_link_state() helper for PF-initiated link
state notifications, also used later by ndo_set_vf_link_state.
Signed-off-by: Satish Kharat <satishkh@cisco.com>
---
drivers/net/ethernet/cisco/enic/enic.h | 7 +-
drivers/net/ethernet/cisco/enic/enic_mbox.c | 190 +++++++++++++++++++++++++++-
drivers/net/ethernet/cisco/enic/enic_mbox.h | 1 +
3 files changed, 194 insertions(+), 4 deletions(-)
diff --git a/drivers/net/ethernet/cisco/enic/enic.h b/drivers/net/ethernet/cisco/enic/enic.h
index b009d87da4bd..d459318c46fc 100644
--- a/drivers/net/ethernet/cisco/enic/enic.h
+++ b/drivers/net/ethernet/cisco/enic/enic.h
@@ -256,9 +256,7 @@ struct enic {
struct enic_rx_coal rx_coalesce_setting;
u32 rx_coalesce_usecs;
u32 tx_coalesce_usecs;
-#ifdef CONFIG_PCI_IOV
u16 num_vfs;
-#endif
enum enic_vf_type vf_type;
unsigned int enable_count;
spinlock_t enic_api_lock;
@@ -315,6 +313,11 @@ struct enic {
/* MBOX protocol state — mbox_lock serializes admin WQ sends */
struct mutex mbox_lock;
u64 mbox_msg_num;
+
+ /* PF: per-VF MBOX state, allocated when SRIOV V2 is enabled */
+ struct enic_vf_state {
+ bool registered;
+ } *vf_state;
};
static inline struct net_device *vnic_get_netdev(struct vnic_dev *vdev)
diff --git a/drivers/net/ethernet/cisco/enic/enic_mbox.c b/drivers/net/ethernet/cisco/enic/enic_mbox.c
index 3709704bee02..b6f05b03ae26 100644
--- a/drivers/net/ethernet/cisco/enic/enic_mbox.c
+++ b/drivers/net/ethernet/cisco/enic/enic_mbox.c
@@ -135,10 +135,183 @@ int enic_mbox_send_msg(struct enic *enic, u8 msg_type, u16 dst_vnic_id,
return err;
}
+int enic_mbox_send_link_state(struct enic *enic, u16 vf_id, u32 link_state)
+{
+ struct enic_mbox_pf_link_state_notif_msg notif = {};
+
+ if (!enic->vf_state || vf_id >= enic->num_vfs ||
+ !enic->vf_state[vf_id].registered) {
+ netdev_dbg(enic->netdev,
+ "MBOX: skip link state to unregistered VF %u\n",
+ vf_id);
+ return 0;
+ }
+
+ notif.link_state = cpu_to_le32(link_state);
+ return enic_mbox_send_msg(enic, ENIC_MBOX_PF_LINK_STATE_NOTIF, vf_id,
+ ¬if, sizeof(notif));
+}
+
+static int enic_mbox_pf_handle_capability(struct enic *enic, void *msg,
+ u16 vf_id, u64 msg_num)
+{
+ struct enic_mbox_vf_capability_reply_msg reply = {};
+
+ reply.reply.ret_major = cpu_to_le16(0);
+ reply.version = cpu_to_le32(ENIC_MBOX_CAP_VERSION_1);
+
+ return enic_mbox_send_msg(enic, ENIC_MBOX_VF_CAPABILITY_REPLY, vf_id,
+ &reply, sizeof(reply));
+}
+
+static int enic_mbox_pf_handle_register(struct enic *enic, void *msg,
+ u16 vf_id, u64 msg_num)
+{
+ struct enic_mbox_vf_register_reply_msg reply = {};
+ u32 link_state;
+ int err;
+
+ if (!enic->vf_state || vf_id >= enic->num_vfs) {
+ if (net_ratelimit())
+ netdev_warn(enic->netdev,
+ "MBOX: register from invalid VF %u\n",
+ vf_id);
+ return -EINVAL;
+ }
+
+ /* VF re-registering (e.g. guest reboot without clean unregister):
+ * mark the previous registration inactive before accepting the new one.
+ */
+ if (enic->vf_state[vf_id].registered) {
+ netdev_dbg(enic->netdev,
+ "MBOX: VF %u re-register, cleaning previous state\n",
+ vf_id);
+ enic->vf_state[vf_id].registered = false;
+ }
+
+ reply.reply.ret_major = cpu_to_le16(0);
+ err = enic_mbox_send_msg(enic, ENIC_MBOX_VF_REGISTER_REPLY, vf_id,
+ &reply, sizeof(reply));
+ if (err)
+ return err;
+
+ enic->vf_state[vf_id].registered = true;
+ if (net_ratelimit())
+ netdev_info(enic->netdev, "VF %u registered via MBOX\n", vf_id);
+
+ link_state = netif_carrier_ok(enic->netdev) ?
+ ENIC_MBOX_LINK_STATE_ENABLE :
+ ENIC_MBOX_LINK_STATE_DISABLE;
+ err = enic_mbox_send_link_state(enic, vf_id, link_state);
+ if (err && net_ratelimit())
+ netdev_warn(enic->netdev,
+ "VF %u: failed to send initial link state: %d\n",
+ vf_id, err);
+ /* Registration succeeded; initial link state notification sent
+ * above. Subsequent link state changes are sent from the PF
+ * when enic_link_check() detects carrier changes.
+ */
+ return 0;
+}
+
+static int enic_mbox_pf_handle_unregister(struct enic *enic, void *msg,
+ u16 vf_id, u64 msg_num)
+{
+ struct enic_mbox_vf_register_reply_msg reply = {};
+ int err;
+
+ if (!enic->vf_state || vf_id >= enic->num_vfs) {
+ if (net_ratelimit())
+ netdev_warn(enic->netdev,
+ "MBOX: unregister from invalid VF %u\n",
+ vf_id);
+ return -EINVAL;
+ }
+
+ /* VF is unloading; clear local state regardless of whether
+ * the reply is successfully delivered to avoid the PF treating
+ * a dead VF as still registered.
+ */
+ enic->vf_state[vf_id].registered = false;
+
+ reply.reply.ret_major = cpu_to_le16(0);
+ err = enic_mbox_send_msg(enic, ENIC_MBOX_VF_UNREGISTER_REPLY, vf_id,
+ &reply, sizeof(reply));
+
+ if (net_ratelimit())
+ netdev_info(enic->netdev,
+ "VF %u unregistered via MBOX\n", vf_id);
+
+ return err;
+}
+
+static void enic_mbox_pf_process_msg(struct enic *enic,
+ struct enic_mbox_hdr *hdr, void *payload)
+{
+ u16 vf_id = le16_to_cpu(hdr->src_vnic_id);
+ u16 msg_len = le16_to_cpu(hdr->msg_len);
+ int err = 0;
+
+ if (!enic->vf_state) {
+ netdev_dbg(enic->netdev,
+ "MBOX: PF received msg but SRIOV not active\n");
+ return;
+ }
+
+ if (vf_id >= enic->num_vfs) {
+ if (net_ratelimit())
+ netdev_warn(enic->netdev,
+ "MBOX: PF received msg from invalid VF %u\n",
+ vf_id);
+ return;
+ }
+
+ switch (hdr->msg_type) {
+ case ENIC_MBOX_VF_CAPABILITY_REQUEST:
+ err = enic_mbox_pf_handle_capability(enic, payload, vf_id,
+ le64_to_cpu(hdr->msg_num));
+ break;
+ case ENIC_MBOX_VF_REGISTER_REQUEST:
+ err = enic_mbox_pf_handle_register(enic, payload, vf_id,
+ le64_to_cpu(hdr->msg_num));
+ break;
+ case ENIC_MBOX_VF_UNREGISTER_REQUEST:
+ err = enic_mbox_pf_handle_unregister(enic, payload, vf_id,
+ le64_to_cpu(hdr->msg_num));
+ break;
+ case ENIC_MBOX_PF_LINK_STATE_ACK: {
+ struct enic_mbox_pf_link_state_ack_msg *ack = payload;
+
+ if (msg_len < sizeof(*hdr) + sizeof(*ack))
+ break;
+ if (le16_to_cpu(ack->ack.ret_major) && net_ratelimit())
+ netdev_warn(enic->netdev,
+ "MBOX: VF %u link state ACK error %u/%u\n",
+ vf_id,
+ le16_to_cpu(ack->ack.ret_major),
+ le16_to_cpu(ack->ack.ret_minor));
+ break;
+ }
+ default:
+ netdev_dbg(enic->netdev,
+ "MBOX: PF unhandled msg type %u from VF %u\n",
+ hdr->msg_type, vf_id);
+ err = -EOPNOTSUPP;
+ break;
+ }
+
+ if (err && net_ratelimit())
+ netdev_warn(enic->netdev,
+ "MBOX: PF handler for msg type %u from VF %u failed: %d\n",
+ hdr->msg_type, vf_id, err);
+}
+
static void enic_mbox_recv_handler(struct enic *enic, void *buf,
unsigned int len)
{
struct enic_mbox_hdr *hdr = buf;
+ void *payload;
+ u16 msg_len;
if (len < sizeof(*hdr)) {
if (net_ratelimit())
@@ -156,10 +329,23 @@ static void enic_mbox_recv_handler(struct enic *enic, void *buf,
return;
}
+ msg_len = le16_to_cpu(hdr->msg_len);
+ if (msg_len < sizeof(*hdr) || msg_len > len) {
+ if (net_ratelimit())
+ netdev_warn(enic->netdev,
+ "MBOX: invalid msg_len %u (buf len %u)\n",
+ msg_len, len);
+ return;
+ }
+
netdev_dbg(enic->netdev,
"MBOX recv: type %u from vnic %u len %u\n",
- hdr->msg_type, le16_to_cpu(hdr->src_vnic_id),
- le16_to_cpu(hdr->msg_len));
+ hdr->msg_type, le16_to_cpu(hdr->src_vnic_id), msg_len);
+
+ payload = buf + sizeof(*hdr);
+
+ if (enic->vf_state)
+ enic_mbox_pf_process_msg(enic, hdr, payload);
}
void enic_mbox_init(struct enic *enic)
diff --git a/drivers/net/ethernet/cisco/enic/enic_mbox.h b/drivers/net/ethernet/cisco/enic/enic_mbox.h
index 73fd7f783ee2..f1de67db1273 100644
--- a/drivers/net/ethernet/cisco/enic/enic_mbox.h
+++ b/drivers/net/ethernet/cisco/enic/enic_mbox.h
@@ -87,5 +87,6 @@ struct enic;
void enic_mbox_init(struct enic *enic);
int enic_mbox_send_msg(struct enic *enic, u8 msg_type, u16 dst_vnic_id,
void *payload, u16 payload_len);
+int enic_mbox_send_link_state(struct enic *enic, u16 vf_id, u32 link_state);
#endif /* _ENIC_MBOX_H_ */
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v12 06/12] enic: define MBOX message types and header structures
From: Satish Kharat @ 2026-07-19 8:41 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni
Cc: netdev, linux-kernel, Breno Leitao, Sesidhar Baddela,
Satish Kharat
In-Reply-To: <20260719-enic-sriov-v2-admin-channel-v2-v12-0-6ff2c617001d@cisco.com>
Define the mailbox protocol structures for PF-VF communication:
message header, generic reply, and per-message-type payloads for
capability negotiation, VF registration/unregistration, and link
state notification/acknowledgment.
Include linux/types.h and linux/bits.h for __le16/__le32/__le64
and BIT() used in the header.
Message types use an even=request / odd=reply convention. The
header carries source and destination VNIC IDs, a monotonically
increasing message number, and the total message length.
Signed-off-by: Satish Kharat <satishkh@cisco.com>
---
drivers/net/ethernet/cisco/enic/enic_mbox.h | 83 +++++++++++++++++++++++++++++
1 file changed, 83 insertions(+)
diff --git a/drivers/net/ethernet/cisco/enic/enic_mbox.h b/drivers/net/ethernet/cisco/enic/enic_mbox.h
new file mode 100644
index 000000000000..a52f1d25cb21
--- /dev/null
+++ b/drivers/net/ethernet/cisco/enic/enic_mbox.h
@@ -0,0 +1,83 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* Copyright 2025 Cisco Systems, Inc. All rights reserved. */
+
+#ifndef _ENIC_MBOX_H_
+#define _ENIC_MBOX_H_
+
+#include <linux/bits.h>
+#include <linux/types.h>
+
+/*
+ * Mailbox protocol for PF-VF communication over the admin channel.
+ *
+ * Even numbers are requests, odd numbers are replies/acks.
+ * The prefix indicates the initiator: VF_ = VF-initiated, PF_ = PF-initiated.
+ */
+enum enic_mbox_msg_type {
+ ENIC_MBOX_VF_CAPABILITY_REQUEST = 0,
+ ENIC_MBOX_VF_CAPABILITY_REPLY = 1,
+ ENIC_MBOX_VF_REGISTER_REQUEST = 2,
+ ENIC_MBOX_VF_REGISTER_REPLY = 3,
+ ENIC_MBOX_VF_UNREGISTER_REQUEST = 4,
+ ENIC_MBOX_VF_UNREGISTER_REPLY = 5,
+ ENIC_MBOX_PF_LINK_STATE_NOTIF = 6,
+ ENIC_MBOX_PF_LINK_STATE_ACK = 7,
+ ENIC_MBOX_MAX
+};
+
+struct enic_mbox_hdr {
+ __le16 src_vnic_id;
+ __le16 dst_vnic_id;
+ u8 msg_type;
+ u8 flags;
+ __le16 msg_len;
+ __le64 msg_num;
+};
+
+struct enic_mbox_generic_reply {
+ __le16 ret_major;
+ __le16 ret_minor;
+};
+
+#define ENIC_MBOX_ERR_GENERIC BIT(0)
+#define ENIC_MBOX_ERR_VF_NOT_REGISTERED BIT(1)
+#define ENIC_MBOX_ERR_MSG_NOT_SUPPORTED BIT(2)
+
+/* ENIC_MBOX_VF_CAPABILITY_REQUEST / _REPLY */
+#define ENIC_MBOX_CAP_VERSION_0 0
+#define ENIC_MBOX_CAP_VERSION_1 1
+
+struct enic_mbox_vf_capability_msg {
+ __le32 version;
+ __le32 reserved[32];
+};
+
+/* The embedded enic_mbox_generic_reply has 2-byte alignment, but the
+ * __le32 members give this struct 4-byte natural alignment. Receive
+ * buffers come from kmalloc (>= 8-byte aligned), so there is no
+ * misaligned access risk when casting from the receive buffer.
+ */
+struct enic_mbox_vf_capability_reply_msg {
+ struct enic_mbox_generic_reply reply;
+ __le32 version;
+ __le32 reserved[32];
+};
+
+/* ENIC_MBOX_VF_REGISTER / _UNREGISTER */
+struct enic_mbox_vf_register_reply_msg {
+ struct enic_mbox_generic_reply reply;
+};
+
+/* ENIC_MBOX_PF_LINK_STATE_NOTIF / _ACK */
+#define ENIC_MBOX_LINK_STATE_DISABLE 0
+#define ENIC_MBOX_LINK_STATE_ENABLE 1
+
+struct enic_mbox_pf_link_state_notif_msg {
+ __le32 link_state;
+};
+
+struct enic_mbox_pf_link_state_ack_msg {
+ struct enic_mbox_generic_reply ack;
+};
+
+#endif /* _ENIC_MBOX_H_ */
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v12 03/12] enic: add admin channel open and close for SR-IOV
From: Satish Kharat @ 2026-07-19 8:41 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni
Cc: netdev, linux-kernel, Breno Leitao, Sesidhar Baddela,
Satish Kharat
In-Reply-To: <20260719-enic-sriov-v2-admin-channel-v2-v12-0-6ff2c617001d@cisco.com>
The V2 SR-IOV design uses a dedicated admin channel (WQ/RQ/CQ/INTR
on separate BAR resources) for PF-VF mailbox communication rather
than firmware-proxied devcmds.
Introduce enic_admin_channel_open() and enic_admin_channel_close().
Open allocates and initialises the admin WQ, RQ, and two CQs (one per
direction), then issues CMD_QP_TYPE_SET to tell firmware the queues are
admin-type. Close reverses the sequence.
enic_admin_wq_buf_clean() unmaps and frees any WQ buffers still held
at close time, fixing a DMA mapping leak when a send times out.
Add CMD_QP_TYPE_SET (97), QP_TYPE_ADMIN/DATA, and QP_ENABLE/QP_DISABLE
defines to vnic_devcmd.h. Add VNIC_CQ_* named constants to vnic_cq.h
so CQ initialisation parameters are self-documenting from their first
introduction.
Signed-off-by: Satish Kharat <satishkh@cisco.com>
---
drivers/net/ethernet/cisco/enic/Makefile | 3 +-
drivers/net/ethernet/cisco/enic/enic.h | 5 +
drivers/net/ethernet/cisco/enic/enic_admin.c | 227 ++++++++++++++++++++++++++
drivers/net/ethernet/cisco/enic/enic_admin.h | 15 ++
drivers/net/ethernet/cisco/enic/vnic_cq.h | 9 +
drivers/net/ethernet/cisco/enic/vnic_devcmd.h | 11 ++
6 files changed, 269 insertions(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/cisco/enic/Makefile b/drivers/net/ethernet/cisco/enic/Makefile
index a96b8332e6e2..7ae72fefc99a 100644
--- a/drivers/net/ethernet/cisco/enic/Makefile
+++ b/drivers/net/ethernet/cisco/enic/Makefile
@@ -3,5 +3,6 @@ obj-$(CONFIG_ENIC) := enic.o
enic-y := enic_main.o vnic_cq.o vnic_intr.o vnic_wq.o \
enic_res.o enic_dev.o enic_pp.o vnic_dev.o vnic_rq.o vnic_vic.o \
- enic_ethtool.o enic_api.o enic_clsf.o enic_rq.o enic_wq.o
+ enic_ethtool.o enic_api.o enic_clsf.o enic_rq.o enic_wq.o \
+ enic_admin.o
diff --git a/drivers/net/ethernet/cisco/enic/enic.h b/drivers/net/ethernet/cisco/enic/enic.h
index 08472420f3a1..398227448b37 100644
--- a/drivers/net/ethernet/cisco/enic/enic.h
+++ b/drivers/net/ethernet/cisco/enic/enic.h
@@ -292,6 +292,11 @@ struct enic {
/* Admin channel resources for SR-IOV MBOX */
bool has_admin_channel;
+ /* true only while the admin WQ/RQ/CQ are allocated and enabled; gates
+ * enic_admin_channel_close() so it is a no-op after a failed (re)open
+ * left the resources freed.
+ */
+ bool admin_chan_up;
struct vnic_wq admin_wq;
struct vnic_rq admin_rq;
struct vnic_cq admin_cq[2];
diff --git a/drivers/net/ethernet/cisco/enic/enic_admin.c b/drivers/net/ethernet/cisco/enic/enic_admin.c
new file mode 100644
index 000000000000..50b46b92c88f
--- /dev/null
+++ b/drivers/net/ethernet/cisco/enic/enic_admin.c
@@ -0,0 +1,227 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright 2025 Cisco Systems, Inc. All rights reserved.
+
+#include <linux/kernel.h>
+#include <linux/netdevice.h>
+
+#include "vnic_dev.h"
+#include "vnic_wq.h"
+#include "vnic_rq.h"
+#include "vnic_cq.h"
+#include "vnic_intr.h"
+#include "vnic_resource.h"
+#include "vnic_devcmd.h"
+#include "enic.h"
+#include "enic_admin.h"
+#include "cq_desc.h"
+#include "wq_enet_desc.h"
+#include "rq_enet_desc.h"
+
+/* Clean up any admin WQ buffers still held by hardware at close time.
+ * Normally buffers are freed inline after send completion, but a timed-out
+ * send intentionally leaves the buffer live until the queue is stopped.
+ */
+static void enic_admin_wq_buf_clean(struct vnic_wq *wq,
+ struct vnic_wq_buf *buf)
+{
+ struct enic *enic = vnic_dev_priv(wq->vdev);
+
+ if (buf->os_buf) {
+ dma_unmap_single(&enic->pdev->dev, buf->dma_addr,
+ buf->len, DMA_TO_DEVICE);
+ kfree(buf->os_buf);
+ buf->os_buf = NULL;
+ }
+}
+
+/* No-op: admin RQ buffer teardown is handled in enic_admin_channel_close */
+static void enic_admin_rq_buf_clean(struct vnic_rq *rq,
+ struct vnic_rq_buf *buf)
+{
+}
+
+static int enic_admin_qp_type_set(struct enic *enic, u32 enable)
+{
+ u64 a0 = QP_TYPE_ADMIN, a1 = enable;
+ int wait = 1000;
+ int err;
+
+ spin_lock_bh(&enic->devcmd_lock);
+ err = vnic_dev_cmd(enic->vdev, CMD_QP_TYPE_SET, &a0, &a1, wait);
+ spin_unlock_bh(&enic->devcmd_lock);
+
+ return err;
+}
+
+static int enic_admin_alloc_resources(struct enic *enic)
+{
+ int err;
+
+ err = vnic_wq_alloc_with_type(enic->vdev, &enic->admin_wq, 0,
+ ENIC_ADMIN_DESC_COUNT,
+ sizeof(struct wq_enet_desc),
+ RES_TYPE_ADMIN_WQ);
+ if (err)
+ return err;
+
+ err = vnic_rq_alloc_with_type(enic->vdev, &enic->admin_rq, 0,
+ ENIC_ADMIN_DESC_COUNT,
+ sizeof(struct rq_enet_desc),
+ RES_TYPE_ADMIN_RQ);
+ if (err)
+ goto free_wq;
+
+ /* admin_cq[0] is the WQ completion queue. WQ CQEs are always
+ * 16 bytes wide; firmware always writes 16-byte CQEs for WQ
+ * completions on every WQ, including the admin channel WQ.
+ * Use sizeof(struct cq_desc) accordingly.
+ */
+ err = vnic_cq_alloc_with_type(enic->vdev, &enic->admin_cq[0], 0,
+ ENIC_ADMIN_DESC_COUNT,
+ sizeof(struct cq_desc),
+ RES_TYPE_ADMIN_CQ);
+ if (err)
+ goto free_rq;
+
+ /* admin_cq[1] is the RQ completion queue. Its descriptor size
+ * must match what firmware writes. enic_ext_cq() called earlier
+ * in probe issues CMD_CQ_ENTRY_SIZE_SET for VNIC_RQ_ALL,
+ * programming firmware to write CQ entries of (16 << enic->ext_cq)
+ * bytes for every RQ CQ on the vNIC, including the admin RQ CQ.
+ * Allocating with the same size keeps the host poller and
+ * firmware in lockstep:
+ *
+ * - The color/valid bit lives at byte (desc_size - 1) of every
+ * cq_enet_rq_desc[_32|_64] variant, so enic_admin_cq_color()
+ * reads it from the correct offset.
+ * - Only the first 15 bytes of the descriptor (vlan,
+ * bytes_written_flags, ...) are accessed by the admin path;
+ * these fields are identical across all three variants (see
+ * comment in enic_rq.c above cq_enet_rq_desc_dec()).
+ */
+ err = vnic_cq_alloc_with_type(enic->vdev, &enic->admin_cq[1], 1,
+ ENIC_ADMIN_DESC_COUNT,
+ 16 << enic->ext_cq,
+ RES_TYPE_ADMIN_CQ);
+ if (err)
+ goto free_cq0;
+
+ return 0;
+
+free_cq0:
+ vnic_cq_free(&enic->admin_cq[0]);
+free_rq:
+ vnic_rq_free(&enic->admin_rq);
+free_wq:
+ vnic_wq_free(&enic->admin_wq);
+ return err;
+}
+
+static void enic_admin_free_resources(struct enic *enic)
+{
+ vnic_cq_free(&enic->admin_cq[1]);
+ vnic_cq_free(&enic->admin_cq[0]);
+ vnic_rq_free(&enic->admin_rq);
+ vnic_wq_free(&enic->admin_wq);
+}
+
+static void enic_admin_init_resources(struct enic *enic)
+{
+ vnic_wq_init(&enic->admin_wq,
+ 0, 0, 0); /* cq_index, err_intr_enable, err_intr_offset */
+ vnic_rq_init(&enic->admin_rq,
+ 1, 0, 0); /* cq_index, err_intr_enable, err_intr_offset */
+ vnic_cq_init(&enic->admin_cq[0],
+ VNIC_CQ_FC_DISABLE,
+ VNIC_CQ_COLOR_ENABLE,
+ 0, 0, 1, /* cq_head, cq_tail, cq_tail_color */
+ VNIC_CQ_INTR_DISABLE,
+ VNIC_CQ_ENTRY_ENABLE,
+ VNIC_CQ_MSG_DISABLE,
+ 0, /* interrupt_offset */
+ 0 /* cq_message_addr */);
+ vnic_cq_init(&enic->admin_cq[1],
+ VNIC_CQ_FC_DISABLE,
+ VNIC_CQ_COLOR_ENABLE,
+ 0, 0, 1, /* cq_head, cq_tail, cq_tail_color */
+ VNIC_CQ_INTR_DISABLE,
+ VNIC_CQ_ENTRY_ENABLE,
+ VNIC_CQ_MSG_DISABLE,
+ 0, /* interrupt_offset */
+ 0 /* cq_message_addr */);
+}
+
+int enic_admin_channel_open(struct enic *enic)
+{
+ int err;
+
+ if (!enic->has_admin_channel)
+ return -ENODEV;
+
+ err = enic_admin_alloc_resources(enic);
+ if (err) {
+ netdev_err(enic->netdev,
+ "Failed to alloc admin channel resources: %d\n",
+ err);
+ return err;
+ }
+
+ enic_admin_init_resources(enic);
+
+ vnic_wq_enable(&enic->admin_wq);
+ vnic_rq_enable(&enic->admin_rq);
+
+ err = enic_admin_qp_type_set(enic, QP_ENABLE);
+ if (err) {
+ netdev_err(enic->netdev,
+ "Failed to set admin QP type: %d\n", err);
+ goto disable_queues;
+ }
+
+ enic->admin_chan_up = true;
+
+ return 0;
+
+disable_queues:
+ enic_admin_qp_type_set(enic, QP_DISABLE);
+ if (vnic_wq_disable(&enic->admin_wq))
+ netdev_warn(enic->netdev, "Failed to disable admin WQ\n");
+ if (vnic_rq_disable(&enic->admin_rq))
+ netdev_warn(enic->netdev, "Failed to disable admin RQ\n");
+ enic_admin_free_resources(enic);
+ return err;
+}
+
+void enic_admin_channel_close(struct enic *enic)
+{
+ int err;
+
+ if (!enic->has_admin_channel)
+ return;
+
+ /* Nothing to tear down if the channel was never (re)opened, e.g. a
+ * failed enic_admin_channel_open() in probe or in the reset path;
+ * otherwise the disable/clean calls below dereference freed resources.
+ */
+ if (!enic->admin_chan_up)
+ return;
+
+ enic_admin_qp_type_set(enic, QP_DISABLE);
+
+ err = vnic_wq_disable(&enic->admin_wq);
+ if (err)
+ netdev_warn(enic->netdev,
+ "Failed to disable admin WQ: %d\n", err);
+ err = vnic_rq_disable(&enic->admin_rq);
+ if (err)
+ netdev_warn(enic->netdev,
+ "Failed to disable admin RQ: %d\n", err);
+
+ vnic_wq_clean(&enic->admin_wq, enic_admin_wq_buf_clean);
+ vnic_rq_clean(&enic->admin_rq, enic_admin_rq_buf_clean);
+ vnic_cq_clean(&enic->admin_cq[0]);
+ vnic_cq_clean(&enic->admin_cq[1]);
+ enic_admin_free_resources(enic);
+
+ enic->admin_chan_up = false;
+}
diff --git a/drivers/net/ethernet/cisco/enic/enic_admin.h b/drivers/net/ethernet/cisco/enic/enic_admin.h
new file mode 100644
index 000000000000..569aadeb9312
--- /dev/null
+++ b/drivers/net/ethernet/cisco/enic/enic_admin.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* Copyright 2025 Cisco Systems, Inc. All rights reserved. */
+
+#ifndef _ENIC_ADMIN_H_
+#define _ENIC_ADMIN_H_
+
+#define ENIC_ADMIN_DESC_COUNT 64
+#define ENIC_ADMIN_BUF_SIZE 2048
+
+struct enic;
+
+int enic_admin_channel_open(struct enic *enic);
+void enic_admin_channel_close(struct enic *enic);
+
+#endif /* _ENIC_ADMIN_H_ */
diff --git a/drivers/net/ethernet/cisco/enic/vnic_cq.h b/drivers/net/ethernet/cisco/enic/vnic_cq.h
index d46d4d2ef6bb..35ffa3230713 100644
--- a/drivers/net/ethernet/cisco/enic/vnic_cq.h
+++ b/drivers/net/ethernet/cisco/enic/vnic_cq.h
@@ -76,6 +76,15 @@ int vnic_cq_alloc(struct vnic_dev *vdev, struct vnic_cq *cq, unsigned int index,
int vnic_cq_alloc_with_type(struct vnic_dev *vdev, struct vnic_cq *cq,
unsigned int index, unsigned int desc_count,
unsigned int desc_size, unsigned int res_type);
+#define VNIC_CQ_FC_ENABLE 1
+#define VNIC_CQ_FC_DISABLE 0
+#define VNIC_CQ_COLOR_ENABLE 1
+#define VNIC_CQ_INTR_ENABLE 1
+#define VNIC_CQ_INTR_DISABLE 0
+#define VNIC_CQ_ENTRY_ENABLE 1
+#define VNIC_CQ_MSG_ENABLE 1
+#define VNIC_CQ_MSG_DISABLE 0
+
void vnic_cq_init(struct vnic_cq *cq, unsigned int flow_control_enable,
unsigned int color_enable, unsigned int cq_head, unsigned int cq_tail,
unsigned int cq_tail_color, unsigned int interrupt_enable,
diff --git a/drivers/net/ethernet/cisco/enic/vnic_devcmd.h b/drivers/net/ethernet/cisco/enic/vnic_devcmd.h
index 3b6efa743dba..90ca06691ebd 100644
--- a/drivers/net/ethernet/cisco/enic/vnic_devcmd.h
+++ b/drivers/net/ethernet/cisco/enic/vnic_devcmd.h
@@ -455,8 +455,19 @@ enum vnic_devcmd_cmd {
*/
CMD_CQ_ENTRY_SIZE_SET = _CMDC(_CMD_DIR_WRITE, _CMD_VTYPE_ENET, 90),
+ /*
+ * Set queue pair type (admin or data)
+ * in: (u32) a0 = queue pair type (0 = admin, 1 = data)
+ * in: (u32) a1 = enable (1) / disable (0)
+ */
+ CMD_QP_TYPE_SET = _CMDC(_CMD_DIR_WRITE, _CMD_VTYPE_ENET, 97),
};
+#define QP_TYPE_ADMIN 0
+#define QP_TYPE_DATA 1
+#define QP_ENABLE 1
+#define QP_DISABLE 0
+
/* CMD_ENABLE2 flags */
#define CMD_ENABLE2_STANDBY 0x0
#define CMD_ENABLE2_ACTIVE 0x1
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v12 01/12] enic: cancel tx_hang_reset work on device removal
From: Satish Kharat @ 2026-07-19 8:41 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni
Cc: netdev, linux-kernel, Breno Leitao, Sesidhar Baddela,
Satish Kharat
In-Reply-To: <20260719-enic-sriov-v2-admin-channel-v2-v12-0-6ff2c617001d@cisco.com>
enic_remove() cancels the reset and change_mtu_work items but does not
cancel tx_hang_reset. A TX timeout that fires while the device is being
removed can schedule enic_tx_hang_reset() so that it runs after
free_netdev(), resulting in a use-after-free.
Cancel tx_hang_reset alongside the other work items before
unregister_netdev().
This is a pre-existing issue, not introduced by the SR-IOV V2 series;
it is included here as an independent fix.
Fixes: 937317c7c109 ("enic: do hang reset only in case of tx timeout")
Signed-off-by: Satish Kharat <satishkh@cisco.com>
---
drivers/net/ethernet/cisco/enic/enic_main.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/net/ethernet/cisco/enic/enic_main.c b/drivers/net/ethernet/cisco/enic/enic_main.c
index e7125b818087..b65796d96efc 100644
--- a/drivers/net/ethernet/cisco/enic/enic_main.c
+++ b/drivers/net/ethernet/cisco/enic/enic_main.c
@@ -3012,6 +3012,7 @@ static void enic_remove(struct pci_dev *pdev)
struct enic *enic = netdev_priv(netdev);
cancel_work_sync(&enic->reset);
+ cancel_work_sync(&enic->tx_hang_reset);
cancel_work_sync(&enic->change_mtu_work);
unregister_netdev(netdev);
enic_dev_deinit(enic);
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v12 05/12] enic: add admin CQ service with MSI-X interrupt and workqueue polling
From: Satish Kharat @ 2026-07-19 8:41 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni
Cc: netdev, linux-kernel, Breno Leitao, Sesidhar Baddela,
Satish Kharat
In-Reply-To: <20260719-enic-sriov-v2-admin-channel-v2-v12-0-6ff2c617001d@cisco.com>
Add completion queue (CQ) service for the admin channel work queue
(WQ) and receive queue (RQ), driven by a dedicated MSI-X interrupt
and a workqueue-based CQ poller.
The admin WQ CQ service advances the completion ring and returns the
number of descriptors consumed. The admin RQ CQ service does the
same for receive completions and copies each received message into a
preallocated buffer. Received messages are enqueued for deferred
dispatch by a separate work_struct so the CQ poller stays short.
When the MSI-X interrupt fires, the ISR schedules the CQ poll
work_struct. The work handler drains all pending completions, kicks
message dispatch if work was done, and returns credits to unmask the
interrupt.
The poll handler snapshots the pending credit count before draining
the CQ so it acknowledges exactly what the hardware reported for this
interrupt; any credits that accrue during draining are serviced by the
next interrupt. At least one credit is returned so the write always
carries a non-zero acknowledgment and re-arms the vector, since the
admin channel is not re-polled like the NAPI data path.
Log a rate-limited warning when admin RQ buffer refill fails so that
transient memory pressure is visible without flooding the log.
Signed-off-by: Satish Kharat <satishkh@cisco.com>
---
drivers/net/ethernet/cisco/enic/enic.h | 8 +
drivers/net/ethernet/cisco/enic/enic_admin.c | 323 ++++++++++++++++++++++++++-
drivers/net/ethernet/cisco/enic/enic_admin.h | 12 +
3 files changed, 339 insertions(+), 4 deletions(-)
diff --git a/drivers/net/ethernet/cisco/enic/enic.h b/drivers/net/ethernet/cisco/enic/enic.h
index 398227448b37..401123e6df1d 100644
--- a/drivers/net/ethernet/cisco/enic/enic.h
+++ b/drivers/net/ethernet/cisco/enic/enic.h
@@ -301,6 +301,14 @@ struct enic {
struct vnic_rq admin_rq;
struct vnic_cq admin_cq[2];
struct vnic_intr admin_intr;
+ struct work_struct admin_poll_work;
+ unsigned int admin_intr_index;
+ struct work_struct admin_msg_work;
+ spinlock_t admin_msg_lock; /* protects admin_msg_list */
+ struct list_head admin_msg_list;
+ unsigned int admin_msg_count; /* current depth of admin_msg_list */
+ void (*admin_rq_handler)(struct enic *enic, void *buf,
+ unsigned int len);
};
static inline struct net_device *vnic_get_netdev(struct vnic_dev *vdev)
diff --git a/drivers/net/ethernet/cisco/enic/enic_admin.c b/drivers/net/ethernet/cisco/enic/enic_admin.c
index b2be42092106..9358165263d9 100644
--- a/drivers/net/ethernet/cisco/enic/enic_admin.c
+++ b/drivers/net/ethernet/cisco/enic/enic_admin.c
@@ -4,6 +4,7 @@
#include <linux/kernel.h>
#include <linux/netdevice.h>
#include <linux/dma-mapping.h>
+#include <linux/interrupt.h>
#include "vnic_dev.h"
#include "vnic_wq.h"
@@ -15,6 +16,7 @@
#include "enic.h"
#include "enic_admin.h"
#include "cq_desc.h"
+#include "cq_enet_desc.h"
#include "wq_enet_desc.h"
#include "rq_enet_desc.h"
@@ -94,6 +96,266 @@ static void enic_admin_rq_drain(struct enic *enic)
vnic_rq_clean(&enic->admin_rq, enic_admin_rq_buf_clean);
}
+static unsigned int enic_admin_cq_color(void *cq_desc, unsigned int desc_size)
+{
+ u8 type_color = *((u8 *)cq_desc + desc_size - 1);
+
+ return (type_color >> CQ_DESC_COLOR_SHIFT) & CQ_DESC_COLOR_MASK;
+}
+
+unsigned int enic_admin_wq_cq_service(struct enic *enic)
+{
+ struct vnic_cq *cq = &enic->admin_cq[0];
+ unsigned int work = 0;
+ void *desc;
+
+ desc = vnic_cq_to_clean(cq);
+ while (enic_admin_cq_color(desc, cq->ring.desc_size) !=
+ cq->last_color) {
+ vnic_cq_inc_to_clean(cq);
+ work++;
+ desc = vnic_cq_to_clean(cq);
+ }
+
+ return work;
+}
+
+/* Upper bound on pending admin messages. A buggy or hostile VF could flood
+ * the PF admin channel faster than admin_msg_work drains it; cap the backlog
+ * so a guest cannot drive the host out of memory.
+ */
+#define ENIC_ADMIN_MSG_MAX 256
+
+static void enic_admin_msg_enqueue(struct enic *enic, void *buf,
+ unsigned int len)
+{
+ struct enic_admin_msg *msg;
+
+ msg = kmalloc(struct_size(msg, data, len), GFP_KERNEL);
+ if (!msg)
+ return;
+
+ msg->len = len;
+ memcpy(msg->data, buf, len);
+
+ spin_lock(&enic->admin_msg_lock);
+ if (enic->admin_msg_count >= ENIC_ADMIN_MSG_MAX) {
+ spin_unlock(&enic->admin_msg_lock);
+ kfree(msg);
+ if (net_ratelimit())
+ netdev_warn(enic->netdev,
+ "admin msg backlog full (%u); dropping\n",
+ ENIC_ADMIN_MSG_MAX);
+ return;
+ }
+ list_add_tail(&msg->list, &enic->admin_msg_list);
+ enic->admin_msg_count++;
+ spin_unlock(&enic->admin_msg_lock);
+}
+
+unsigned int enic_admin_rq_cq_service(struct enic *enic)
+{
+ struct vnic_cq *cq = &enic->admin_cq[1];
+ struct vnic_rq *rq = &enic->admin_rq;
+ struct cq_enet_rq_desc *rq_desc;
+ struct vnic_rq_buf *buf;
+ u16 bwf, bytes_written;
+ unsigned int work = 0;
+ void *desc;
+
+ /* The admin RQ and its CQ form a single in-order channel: firmware
+ * posts exactly one CQE per consumed RQ descriptor, in submission
+ * order. Each CQE therefore pairs with rq->to_clean below without a
+ * completed_index cross-check, mirroring the in-order assumption of
+ * the main enic RX path.
+ */
+ desc = vnic_cq_to_clean(cq);
+ while (enic_admin_cq_color(desc, cq->ring.desc_size) !=
+ cq->last_color) {
+ /* Ensure DMA descriptor fields are read after
+ * the color/valid check. dma_rmb() is the
+ * correct barrier for DMA-written descriptors.
+ */
+ dma_rmb();
+ buf = rq->to_clean;
+
+ /* Decode the actual number of bytes hardware wrote into
+ * the RX buffer. buf->len is the static allocation size
+ * (ENIC_ADMIN_BUF_SIZE) and would expose uninitialised
+ * heap memory beyond the real payload. bytes_written_flags
+ * is at the same offset in every cq_enet_rq_desc[_32|_64]
+ * variant.
+ */
+ rq_desc = desc;
+ bwf = le16_to_cpu(rq_desc->bytes_written_flags);
+ bytes_written = bwf & CQ_ENET_RQ_DESC_BYTES_WRITTEN_MASK;
+ if (bytes_written > buf->len)
+ goto next_desc;
+
+ dma_sync_single_for_cpu(&enic->pdev->dev,
+ buf->dma_addr, buf->len,
+ DMA_FROM_DEVICE);
+
+ /* Drop on hardware error indications. Admin messages
+ * are internal to the VIC, not received over the wire.
+ * Firmware sets TRUNCATED when the message does not fit
+ * in the posted buffer, and FCS_OK is always set on
+ * healthy admin completions.
+ */
+ if (bwf & CQ_ENET_RQ_DESC_FLAGS_TRUNCATED) {
+ netdev_warn_once(enic->netdev,
+ "admin RQ: truncated message dropped\n");
+ goto next_desc;
+ }
+ if (!(rq_desc->flags & CQ_ENET_RQ_DESC_FLAGS_FCS_OK)) {
+ netdev_warn_once(enic->netdev,
+ "admin RQ: bad FCS, dropping message\n");
+ goto next_desc;
+ }
+
+ enic_admin_msg_enqueue(enic, buf->os_buf, bytes_written);
+
+next_desc:
+ enic_admin_rq_buf_clean(rq, rq->to_clean);
+ rq->to_clean = rq->to_clean->next;
+ rq->ring.desc_avail++;
+
+ vnic_cq_inc_to_clean(cq);
+ work++;
+ desc = vnic_cq_to_clean(cq);
+ }
+
+ if (enic_admin_rq_fill(enic, GFP_KERNEL) && net_ratelimit())
+ netdev_warn(enic->netdev,
+ "admin RQ refill failed\n");
+
+ return work;
+}
+
+static irqreturn_t enic_admin_isr_msix(int irq, void *data)
+{
+ struct enic *enic = data;
+
+ schedule_work(&enic->admin_poll_work);
+
+ return IRQ_HANDLED;
+}
+
+static void enic_admin_msg_work_handler(struct work_struct *work)
+{
+ struct enic *enic = container_of(work, struct enic, admin_msg_work);
+ struct enic_admin_msg *msg, *tmp;
+ LIST_HEAD(local_list);
+
+ spin_lock_bh(&enic->admin_msg_lock);
+ list_splice_init(&enic->admin_msg_list, &local_list);
+ enic->admin_msg_count = 0;
+ spin_unlock_bh(&enic->admin_msg_lock);
+
+ list_for_each_entry_safe(msg, tmp, &local_list, list) {
+ if (enic->admin_rq_handler)
+ enic->admin_rq_handler(enic, msg->data, msg->len);
+ list_del(&msg->list);
+ kfree(msg);
+ }
+}
+
+static void enic_admin_poll_work_handler(struct work_struct *work)
+{
+ struct enic *enic = container_of(work, struct enic, admin_poll_work);
+ unsigned int credits;
+ unsigned int rq_work;
+
+ /* Snapshot the pending credit count before draining so we acknowledge
+ * exactly what the hardware reported for this interrupt. Credits that
+ * accrue while enic_admin_rq_cq_service() runs are left for the next
+ * interrupt, which is harmless on this low-rate control path.
+ */
+ credits = vnic_intr_credits(&enic->admin_intr);
+
+ rq_work = enic_admin_rq_cq_service(enic);
+
+ if (rq_work > 0)
+ schedule_work(&enic->admin_msg_work);
+
+ /* Acknowledge the snapshotted credits and unmask the vector. Unlike
+ * the NAPI data path, the admin channel is not re-polled, so the vector
+ * must be re-armed here to receive the next completion. The unmask is
+ * applied through the interrupt mask register independently of the
+ * credit count, so returning zero credits on a spurious wakeup still
+ * re-arms the vector.
+ */
+ vnic_intr_return_credits(&enic->admin_intr,
+ credits,
+ 1 /* unmask */, 0);
+}
+
+static int enic_admin_setup_intr(struct enic *enic)
+{
+ unsigned int intr_index = enic->intr_count;
+ int err;
+
+ if (vnic_dev_get_intr_mode(enic->vdev) != VNIC_DEV_INTR_MODE_MSIX ||
+ intr_index >= enic->intr_avail)
+ return -ENODEV;
+
+ /* The admin INTR uses a slot in the same RES_TYPE_INTR_CTRL
+ * strided array of per-vector control blocks (mask, coalescing
+ * timer, credit return) that the data-path IRQs occupy in BAR0.
+ * vnic_intr_alloc() defaults to RES_TYPE_INTR_CTRL, which is what
+ * we want here.
+ */
+ err = vnic_intr_alloc(enic->vdev, &enic->admin_intr, intr_index);
+ if (err) {
+ netdev_warn(enic->netdev,
+ "Failed to alloc admin intr at index %u: %d\n",
+ intr_index, err);
+ return err;
+ }
+
+ enic->admin_intr_index = intr_index;
+
+ /* A V2 VF opens the admin channel during probe, before
+ * register_netdev() resolves the "eth%d" name template, so using
+ * netdev->name here would register the literal "eth%d-admin" in
+ * /proc/interrupts. Use the already-stable PCI device name instead.
+ */
+ snprintf(enic->msix[intr_index].devname,
+ sizeof(enic->msix[intr_index].devname),
+ "%s-admin", pci_name(enic->pdev));
+ enic->msix[intr_index].isr = enic_admin_isr_msix;
+ enic->msix[intr_index].devid = enic;
+
+ err = request_irq(enic->msix_entry[intr_index].vector,
+ enic->msix[intr_index].isr, 0,
+ enic->msix[intr_index].devname,
+ enic->msix[intr_index].devid);
+ if (err) {
+ netdev_warn(enic->netdev,
+ "Failed to request admin MSI-X irq: %d\n", err);
+ vnic_intr_free(&enic->admin_intr);
+ return err;
+ }
+
+ enic->msix[intr_index].requested = 1;
+
+ netdev_dbg(enic->netdev,
+ "admin channel using MSI-X interrupt (index %u)\n",
+ intr_index);
+
+ return 0;
+}
+
+static void enic_admin_teardown_intr(struct enic *enic)
+{
+ unsigned int intr_index = enic->admin_intr_index;
+
+ free_irq(enic->msix_entry[intr_index].vector,
+ enic->msix[intr_index].devid);
+ cancel_work_sync(&enic->admin_poll_work);
+ enic->msix[intr_index].requested = 0;
+}
+
static int enic_admin_qp_type_set(struct enic *enic, u32 enable)
{
u64 a0 = QP_TYPE_ADMIN, a1 = enable;
@@ -173,6 +435,7 @@ static int enic_admin_alloc_resources(struct enic *enic)
static void enic_admin_free_resources(struct enic *enic)
{
+ vnic_intr_free(&enic->admin_intr);
vnic_cq_free(&enic->admin_cq[1]);
vnic_cq_free(&enic->admin_cq[0]);
vnic_rq_free(&enic->admin_rq);
@@ -181,6 +444,8 @@ static void enic_admin_free_resources(struct enic *enic)
static void enic_admin_init_resources(struct enic *enic)
{
+ unsigned int intr_offset = enic->admin_intr_index;
+
vnic_wq_init(&enic->admin_wq,
0, 0, 0); /* cq_index, err_intr_enable, err_intr_offset */
vnic_rq_init(&enic->admin_rq,
@@ -189,20 +454,35 @@ static void enic_admin_init_resources(struct enic *enic)
VNIC_CQ_FC_DISABLE,
VNIC_CQ_COLOR_ENABLE,
0, 0, 1, /* cq_head, cq_tail, cq_tail_color */
- VNIC_CQ_INTR_DISABLE,
+ VNIC_CQ_INTR_DISABLE, /* polled synchronously by mbox send */
VNIC_CQ_ENTRY_ENABLE,
VNIC_CQ_MSG_DISABLE,
- 0, /* interrupt_offset */
+ intr_offset,
0 /* cq_message_addr */);
vnic_cq_init(&enic->admin_cq[1],
VNIC_CQ_FC_DISABLE,
VNIC_CQ_COLOR_ENABLE,
0, 0, 1, /* cq_head, cq_tail, cq_tail_color */
- VNIC_CQ_INTR_DISABLE,
+ VNIC_CQ_INTR_ENABLE,
VNIC_CQ_ENTRY_ENABLE,
VNIC_CQ_MSG_DISABLE,
- 0, /* interrupt_offset */
+ intr_offset,
0 /* cq_message_addr */);
+ vnic_intr_init(&enic->admin_intr,
+ 0, 0, 1); /* coalescing_timer, coalescing_type, mask_on_assertion */
+}
+
+static void enic_admin_msg_drain(struct enic *enic)
+{
+ struct enic_admin_msg *msg, *tmp;
+
+ spin_lock_bh(&enic->admin_msg_lock);
+ list_for_each_entry_safe(msg, tmp, &enic->admin_msg_list, list) {
+ list_del(&msg->list);
+ kfree(msg);
+ }
+ enic->admin_msg_count = 0;
+ spin_unlock_bh(&enic->admin_msg_lock);
}
int enic_admin_channel_open(struct enic *enic)
@@ -220,6 +500,19 @@ int enic_admin_channel_open(struct enic *enic)
return err;
}
+ spin_lock_init(&enic->admin_msg_lock);
+ INIT_LIST_HEAD(&enic->admin_msg_list);
+ INIT_WORK(&enic->admin_msg_work, enic_admin_msg_work_handler);
+ INIT_WORK(&enic->admin_poll_work, enic_admin_poll_work_handler);
+
+ err = enic_admin_setup_intr(enic);
+ if (err) {
+ netdev_err(enic->netdev,
+ "Admin channel requires MSI-X, SR-IOV unavailable: %d\n",
+ err);
+ goto free_resources;
+ }
+
enic_admin_init_resources(enic);
vnic_wq_enable(&enic->admin_wq);
@@ -239,17 +532,31 @@ int enic_admin_channel_open(struct enic *enic)
goto disable_queues;
}
+ vnic_intr_unmask(&enic->admin_intr);
+
+ netdev_dbg(enic->netdev,
+ "admin channel open: intr=%u wq_avail=%u rq_avail=%u cq0_color=%u cq1_color=%u\n",
+ enic->admin_intr_index,
+ vnic_wq_desc_avail(&enic->admin_wq),
+ vnic_rq_desc_avail(&enic->admin_rq),
+ enic->admin_cq[0].last_color,
+ enic->admin_cq[1].last_color);
+
enic->admin_chan_up = true;
return 0;
disable_queues:
+ enic_admin_teardown_intr(enic);
enic_admin_qp_type_set(enic, QP_DISABLE);
if (vnic_wq_disable(&enic->admin_wq))
netdev_warn(enic->netdev, "Failed to disable admin WQ\n");
if (vnic_rq_disable(&enic->admin_rq))
netdev_warn(enic->netdev, "Failed to disable admin RQ\n");
+ cancel_work_sync(&enic->admin_msg_work);
+ enic_admin_msg_drain(enic);
enic_admin_rq_drain(enic);
+free_resources:
enic_admin_free_resources(enic);
return err;
}
@@ -268,6 +575,13 @@ void enic_admin_channel_close(struct enic *enic)
if (!enic->admin_chan_up)
return;
+ netdev_dbg(enic->netdev, "admin channel close\n");
+
+ vnic_intr_mask(&enic->admin_intr);
+ enic_admin_teardown_intr(enic);
+ cancel_work_sync(&enic->admin_msg_work);
+ enic_admin_msg_drain(enic);
+
enic_admin_qp_type_set(enic, QP_DISABLE);
err = vnic_wq_disable(&enic->admin_wq);
@@ -283,6 +597,7 @@ void enic_admin_channel_close(struct enic *enic)
enic_admin_rq_drain(enic);
vnic_cq_clean(&enic->admin_cq[0]);
vnic_cq_clean(&enic->admin_cq[1]);
+ vnic_intr_clean(&enic->admin_intr);
enic_admin_free_resources(enic);
enic->admin_chan_up = false;
diff --git a/drivers/net/ethernet/cisco/enic/enic_admin.h b/drivers/net/ethernet/cisco/enic/enic_admin.h
index 569aadeb9312..62c80220b0ca 100644
--- a/drivers/net/ethernet/cisco/enic/enic_admin.h
+++ b/drivers/net/ethernet/cisco/enic/enic_admin.h
@@ -9,7 +9,19 @@
struct enic;
+/* Wrapper for received admin messages queued for deferred processing.
+ * The admin CQ poll work handler enqueues these; a separate work handler
+ * processes them where sleeping (mutex, GFP_KERNEL) is safe.
+ */
+struct enic_admin_msg {
+ struct list_head list;
+ unsigned int len;
+ u8 data[] __aligned(8);
+};
+
int enic_admin_channel_open(struct enic *enic);
void enic_admin_channel_close(struct enic *enic);
+unsigned int enic_admin_wq_cq_service(struct enic *enic);
+unsigned int enic_admin_rq_cq_service(struct enic *enic);
#endif /* _ENIC_ADMIN_H_ */
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v12 11/12] enic: add V2 VF probe with admin channel and PF registration
From: Satish Kharat @ 2026-07-19 8:41 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni
Cc: netdev, linux-kernel, Breno Leitao, Sesidhar Baddela,
Satish Kharat
In-Reply-To: <20260719-enic-sriov-v2-admin-channel-v2-v12-0-6ff2c617001d@cisco.com>
When a V2 SR-IOV VF probes, open the admin channel, initialize the
MBOX protocol, perform the capability check with the PF, and register
with the PF. This establishes the PF-VF communication path that the PF
uses to send link state notifications.
The admin channel and MBOX registration happen after enic_dev_init()
(which discovers admin channel resources) and before register_netdev()
so the VF is fully initialized before the interface is visible to
userspace.
A V2 VF whose firmware did not provision admin WQ/RQ/CQ resources
fails probe with -ENODEV from enic_admin_channel_open(); the admin
channel is a hard requirement for V2 VFs.
The admin channel is opened before enic_mbox_init() installs the
receive handler. This is safe because enic_admin_rq_cq_service()
checks admin_rq_handler before enqueuing received buffers, so any
interrupt that fires between open and mbox_init is harmlessly
discarded.
On remove, the VF unregisters from the PF and closes its admin channel
before tearing down data path resources.
V2 VFs are not provisioned with an RES_TYPE_SRIOV_INTR resource by
firmware, so bypass that check in the admin channel capability
detection for V2 VFs. The PF still requires this resource.
The admin MSI-X vector reserved by enic_set_intr_mode()
is used for the admin channel interrupt.
enic_adjust_resources() ensures the reserved slot is within
intr_avail bounds even at maximum queue configurations. The
admin INTR uses a RES_TYPE_INTR_CTRL slot shared with the
data path.
Signed-off-by: Satish Kharat <satishkh@cisco.com>
---
drivers/net/ethernet/cisco/enic/enic.h | 1 +
drivers/net/ethernet/cisco/enic/enic_main.c | 101 +++++++++++++++++++++++++---
drivers/net/ethernet/cisco/enic/enic_res.c | 3 +-
3 files changed, 94 insertions(+), 11 deletions(-)
diff --git a/drivers/net/ethernet/cisco/enic/enic.h b/drivers/net/ethernet/cisco/enic/enic.h
index b5a43fe04877..62b8941489d7 100644
--- a/drivers/net/ethernet/cisco/enic/enic.h
+++ b/drivers/net/ethernet/cisco/enic/enic.h
@@ -452,6 +452,7 @@ void enic_reset_addr_lists(struct enic *enic);
int enic_sriov_enabled(struct enic *enic);
int enic_is_valid_vf(struct enic *enic, int vf);
int enic_is_dynamic(struct enic *enic);
+int enic_is_sriov_vf_v2(struct enic *enic);
void enic_set_ethtool_ops(struct net_device *netdev);
int __enic_set_rsskey(struct enic *enic);
void enic_ext_cq(struct enic *enic);
diff --git a/drivers/net/ethernet/cisco/enic/enic_main.c b/drivers/net/ethernet/cisco/enic/enic_main.c
index 68a293d60ab8..99c06a049800 100644
--- a/drivers/net/ethernet/cisco/enic/enic_main.c
+++ b/drivers/net/ethernet/cisco/enic/enic_main.c
@@ -316,6 +316,11 @@ static int enic_is_sriov_vf(struct enic *enic)
enic->pdev->device == PCI_DEVICE_ID_CISCO_VIC_ENET_VF_V2;
}
+int enic_is_sriov_vf_v2(struct enic *enic)
+{
+ return enic->pdev->device == PCI_DEVICE_ID_CISCO_VIC_ENET_VF_V2;
+}
+
int enic_is_valid_vf(struct enic *enic, int vf)
{
#ifdef CONFIG_PCI_IOV
@@ -2399,15 +2404,19 @@ static int enic_adjust_resources(struct enic *enic)
enic->intr_count = enic->intr_avail;
break;
case VNIC_DEV_INTR_MODE_MSIX: {
- /* Reserve one MSI-X slot for the admin channel interrupt
- * when V2 SR-IOV admin channel resources are present.
- */
- unsigned int admin_reserve =
- enic->has_admin_channel ? 1 : 0;
-
/* Adjust the number of wqs/rqs/cqs/interrupts that will be
- * used based on which resource is the most constrained
+ * used based on which resource is the most constrained.
+ * Reserve one extra MSI-X slot for the admin channel INTR
+ * when has_admin_channel is set so that
+ * enic_admin_setup_intr() can allocate at intr_count
+ * within the intr_avail bounds even when the data queue
+ * count is maxed out. intr_count counts only the data-path
+ * IRQs (registered by enic_request_intr()); the admin INTR
+ * lives at msix index intr_count and is set up later by
+ * enic_admin_setup_intr().
*/
+ unsigned int admin_reserve = enic->has_admin_channel ? 1 : 0;
+
wq_avail = min(enic->wq_avail, ENIC_WQ_MAX);
rq_default = max(netif_get_num_default_rss_queues(),
ENIC_RQ_MIN_DEFAULT);
@@ -3104,6 +3113,44 @@ static int enic_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
goto err_out_dev_close;
}
+ /* Initialise link_notify_work before the V2-VF admin-open block below:
+ * its error path (err_out_admin_close -> enic_admin_channel_close() ->
+ * cancel_work_sync()) would otherwise act on an uninitialised work.
+ */
+ INIT_WORK(&enic->link_notify_work, enic_link_notify_work_handler);
+
+ /* V2 VF: open admin channel and register with PF.
+ * Must happen before register_netdev so the VF is fully
+ * initialized before the interface is visible to userspace.
+ *
+ * admin_channel_open() runs before enic_mbox_init() installs
+ * the receive handler. This is safe because
+ * enic_admin_rq_cq_service() checks admin_rq_handler before
+ * enqueuing any received buffer, so interrupts that fire
+ * between open and mbox_init are harmlessly discarded.
+ */
+ if (enic_is_sriov_vf_v2(enic)) {
+ err = enic_admin_channel_open(enic);
+ if (err) {
+ dev_err(dev,
+ "Failed to open admin channel: %d\n", err);
+ goto err_out_dev_deinit;
+ }
+ enic_mbox_init(enic);
+ err = enic_mbox_vf_capability_check(enic);
+ if (err) {
+ dev_err(dev,
+ "MBOX capability check failed: %d\n", err);
+ goto err_out_admin_close;
+ }
+ err = enic_mbox_vf_register(enic);
+ if (err) {
+ dev_err(dev,
+ "MBOX VF registration failed: %d\n", err);
+ goto err_out_admin_close;
+ }
+ }
+
netif_set_real_num_tx_queues(netdev, enic->wq_count);
netif_set_real_num_rx_queues(netdev, enic->rq_count);
@@ -3116,7 +3163,6 @@ static int enic_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
INIT_WORK(&enic->reset, enic_reset);
INIT_WORK(&enic->tx_hang_reset, enic_tx_hang_reset);
INIT_WORK(&enic->change_mtu_work, enic_change_mtu_work);
- INIT_WORK(&enic->link_notify_work, enic_link_notify_work_handler);
for (i = 0; i < enic->wq_count; i++)
spin_lock_init(&enic->wq[i].lock);
@@ -3129,7 +3175,7 @@ static int enic_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
err = enic_set_mac_addr(netdev, enic->mac_addr);
if (err) {
dev_err(dev, "Invalid MAC address, aborting\n");
- goto err_out_dev_deinit;
+ goto err_out_admin_close;
}
enic->tx_coalesce_usecs = enic->config.intr_timer_usec;
@@ -3227,11 +3273,23 @@ static int enic_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
err = register_netdev(netdev);
if (err) {
dev_err(dev, "Cannot register net device, aborting\n");
- goto err_out_dev_deinit;
+ goto err_out_admin_close;
}
return 0;
+err_out_admin_close:
+ if (enic_is_sriov_vf_v2(enic)) {
+ if (enic->vf_registered) {
+ int unreg_err = enic_mbox_vf_unregister(enic);
+
+ if (unreg_err)
+ netdev_warn(netdev,
+ "Failed to unregister from PF: %d\n",
+ unreg_err);
+ }
+ enic_admin_channel_close(enic);
+ }
err_out_dev_deinit:
enic_dev_deinit(enic);
err_out_dev_close:
@@ -3269,7 +3327,30 @@ static void enic_remove(struct pci_dev *pdev)
cancel_work_sync(&enic->reset);
cancel_work_sync(&enic->tx_hang_reset);
cancel_work_sync(&enic->change_mtu_work);
+
+ /* Close the admin channel and unregister from the PF before
+ * unregister_netdev() to prevent a late PF notification from
+ * touching a netdev that is being torn down.
+ */
+ if (enic_is_sriov_vf_v2(enic)) {
+ if (enic->vf_registered) {
+ int unreg_err = enic_mbox_vf_unregister(enic);
+
+ if (unreg_err)
+ netdev_warn(netdev,
+ "Failed to unregister from PF: %d\n",
+ unreg_err);
+ }
+ enic_admin_channel_close(enic);
+ }
+
unregister_netdev(netdev);
+ /* unregister_netdev() -> enic_stop() stops the notify timer, so
+ * no new link_notify_work can be queued past this point. Cancel
+ * unconditionally to cover the narrow window where
+ * enic_link_check() scheduled it just as SR-IOV was disabled.
+ */
+ cancel_work_sync(&enic->link_notify_work);
#ifdef CONFIG_PCI_IOV
if (enic_sriov_enabled(enic)) {
if (enic->vf_type == ENIC_VF_TYPE_V2)
diff --git a/drivers/net/ethernet/cisco/enic/enic_res.c b/drivers/net/ethernet/cisco/enic/enic_res.c
index 436326ace049..74cd2ee3af5c 100644
--- a/drivers/net/ethernet/cisco/enic/enic_res.c
+++ b/drivers/net/ethernet/cisco/enic/enic_res.c
@@ -211,7 +211,8 @@ void enic_get_res_counts(struct enic *enic)
vnic_dev_get_res_count(enic->vdev, RES_TYPE_ADMIN_RQ) >= 1 &&
vnic_dev_get_res_count(enic->vdev, RES_TYPE_ADMIN_CQ) >=
ARRAY_SIZE(enic->admin_cq) &&
- vnic_dev_get_res_count(enic->vdev, RES_TYPE_SRIOV_INTR) >= 1;
+ (enic_is_sriov_vf_v2(enic) ||
+ vnic_dev_get_res_count(enic->vdev, RES_TYPE_SRIOV_INTR) >= 1);
dev_info(enic_get_dev(enic),
"vNIC resources avail: wq %d rq %d cq %d intr %d admin %s\n",
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v12 10/12] enic: wire V2 SR-IOV enable with admin channel and MBOX
From: Satish Kharat @ 2026-07-19 8:41 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni
Cc: netdev, linux-kernel, Breno Leitao, Sesidhar Baddela,
Satish Kharat
In-Reply-To: <20260719-enic-sriov-v2-admin-channel-v2-v12-0-6ff2c617001d@cisco.com>
Extend enic_sriov_configure() to handle V2 SR-IOV VFs. When the PF
detects V2 VF device IDs, the enable path allocates per-VF MBOX state,
opens the admin channel, initializes the MBOX protocol, and then calls
pci_enable_sriov(). The admin channel must be ready before VFs are
created so that VF drivers can immediately begin the MBOX capability
and registration handshake during their probe.
The enic_sriov_configure() dispatcher and its V2 helpers
(enic_sriov_v2_enable, enic_sriov_v2_disable) are defined here but
intentionally not yet wired into struct pci_driver via
.sriov_configure -- hence the __maybe_unused annotations. This
series introduces only the admin channel and MBOX infrastructure;
sysfs-driven V2 enable/disable will be activated in a follow-up
patch by adding ".sriov_configure = enic_sriov_configure," to
enic_driver.
Because .sriov_configure is not registered yet, enic_sriov_configure()
cannot run concurrently with the rtnl-protected reset paths
(enic_reset(), enic_tx_hang_reset()) in this series, so there is no
reachable locking race between SR-IOV enable/disable and reset. The
follow-up patch that wires the callback will add the necessary
serialization against those paths. Note that simply taking rtnl_lock()
around the enable path is not viable, because pci_enable_sriov()
triggers VF probe and register_netdev(), which themselves acquire rtnl;
the wiring patch therefore uses finer-grained serialization.
The disable path first clears ENIC_SRIOV_ENABLED and flushes the
link-notify work, so no further VF link-state broadcast can run, then
calls pci_disable_sriov() (VF drivers unregister via MBOX), closes the
admin channel, and frees per-VF state. Clearing the flag and flushing
the work before vf_state is freed closes a use-after-free window
against the link-notify path.
Notify registered VFs of PF link transitions: enic_link_check()
schedules link_notify_work on each carrier up/down edge, and the work
handler sends PF_LINK_STATE_NOTIF to the VFs from process context.
The broadcast cannot run directly in enic_link_check() because the
MBOX send path may sleep and link check runs in the notify timer/ISR
context.
Re-establish the admin/MBOX channel across a PF reset. enic_reset()
and enic_tx_hang_reset() fully close the admin channel before the
soft/hang reset (which wipes all hardware queues, including the admin
WQ/RQ), then reopen it and re-run enic_mbox_init() after the data path
is back up, and re-push the current link state to registered VFs.
Reject VF port profile requests when V2 SR-IOV is active
(enic_is_valid_pp_vf), since enic->pp is not reallocated for V2 VFs
and the V2 protocol uses MBOX instead of port profiles.
Update enic_remove() to run enic_dev_deinit() and vnic_dev_close()
after SR-IOV teardown, so the PF device remains functional while VFs
are being cleaned up. This ordering applies to both V1 and V2 SR-IOV
paths.
Signed-off-by: Satish Kharat <satishkh@cisco.com>
---
drivers/net/ethernet/cisco/enic/enic.h | 2 +
drivers/net/ethernet/cisco/enic/enic_admin.c | 3 +
drivers/net/ethernet/cisco/enic/enic_main.c | 260 +++++++++++++++++++++++++--
drivers/net/ethernet/cisco/enic/enic_mbox.c | 13 +-
drivers/net/ethernet/cisco/enic/enic_pp.c | 5 +
drivers/net/ethernet/cisco/enic/enic_res.c | 1 +
drivers/net/ethernet/cisco/enic/vnic_enet.h | 4 +-
7 files changed, 274 insertions(+), 14 deletions(-)
diff --git a/drivers/net/ethernet/cisco/enic/enic.h b/drivers/net/ethernet/cisco/enic/enic.h
index a9a376d2cf0e..b5a43fe04877 100644
--- a/drivers/net/ethernet/cisco/enic/enic.h
+++ b/drivers/net/ethernet/cisco/enic/enic.h
@@ -305,6 +305,7 @@ struct enic {
struct vnic_intr admin_intr;
struct work_struct admin_poll_work;
unsigned int admin_intr_index;
+ struct work_struct link_notify_work;
struct work_struct admin_msg_work;
spinlock_t admin_msg_lock; /* protects admin_msg_list */
struct list_head admin_msg_list;
@@ -324,6 +325,7 @@ struct enic {
*/
struct completion mbox_comp;
u8 mbox_expected_reply;
+ bool mbox_initialized;
/* PF: per-VF MBOX state, allocated when SRIOV V2 is enabled */
struct enic_vf_state {
diff --git a/drivers/net/ethernet/cisco/enic/enic_admin.c b/drivers/net/ethernet/cisco/enic/enic_admin.c
index 3e7103f2afc1..6642d76cd8c8 100644
--- a/drivers/net/ethernet/cisco/enic/enic_admin.c
+++ b/drivers/net/ethernet/cisco/enic/enic_admin.c
@@ -617,6 +617,7 @@ void enic_admin_channel_close(struct enic *enic)
vnic_intr_mask(&enic->admin_intr);
enic_admin_teardown_intr(enic);
+ cancel_work_sync(&enic->link_notify_work);
cancel_work_sync(&enic->admin_msg_work);
enic_admin_msg_drain(enic);
@@ -636,6 +637,8 @@ void enic_admin_channel_close(struct enic *enic)
vnic_cq_clean(&enic->admin_cq[0]);
vnic_cq_clean(&enic->admin_cq[1]);
vnic_intr_clean(&enic->admin_intr);
+
+ enic->admin_rq_handler = NULL;
enic_admin_free_resources(enic);
enic->admin_chan_up = false;
diff --git a/drivers/net/ethernet/cisco/enic/enic_main.c b/drivers/net/ethernet/cisco/enic/enic_main.c
index 6992411bd3b5..68a293d60ab8 100644
--- a/drivers/net/ethernet/cisco/enic/enic_main.c
+++ b/drivers/net/ethernet/cisco/enic/enic_main.c
@@ -60,6 +60,8 @@
#include "enic_clsf.h"
#include "enic_rq.h"
#include "enic_wq.h"
+#include "enic_admin.h"
+#include "enic_mbox.h"
#define ENIC_NOTIFY_TIMER_PERIOD (2 * HZ)
@@ -411,6 +413,24 @@ static void enic_set_rx_coal_setting(struct enic *enic)
rx_coal->use_adaptive_rx_coalesce = 1;
}
+static void enic_link_notify_work_handler(struct work_struct *work)
+{
+ struct enic *enic = container_of(work, struct enic,
+ link_notify_work);
+ u32 state;
+ u16 i;
+
+ if (!enic_sriov_enabled(enic) || !enic->vf_state)
+ return;
+
+ state = netif_carrier_ok(enic->netdev) ?
+ ENIC_MBOX_LINK_STATE_ENABLE :
+ ENIC_MBOX_LINK_STATE_DISABLE;
+
+ for (i = 0; i < enic->num_vfs; i++)
+ enic_mbox_send_link_state(enic, i, state);
+}
+
static void enic_link_check(struct enic *enic)
{
int link_status = vnic_dev_link_status(enic->vdev);
@@ -420,9 +440,13 @@ static void enic_link_check(struct enic *enic)
netdev_info(enic->netdev, "Link UP\n");
netif_carrier_on(enic->netdev);
enic_set_rx_coal_setting(enic);
+ if (enic_sriov_enabled(enic) && enic->vf_state)
+ schedule_work(&enic->link_notify_work);
} else if (!link_status && carrier_ok) {
netdev_info(enic->netdev, "Link DOWN\n");
netif_carrier_off(enic->netdev);
+ if (enic_sriov_enabled(enic) && enic->vf_state)
+ schedule_work(&enic->link_notify_work);
}
}
@@ -2154,15 +2178,47 @@ static void enic_reset(struct work_struct *work)
/* Stop any activity from infiniband */
enic_set_api_busy(enic, true);
+ /* Fully tear down the V2 admin/MBOX channel before the soft reset.
+ * The reset wipes all hardware queues including the admin WQ/RQ;
+ * closing first tells firmware to stop the admin QP (so it no longer
+ * DMAs from the about-to-be-reset rings) and frees the admin resources
+ * so they are cleanly re-allocated afterwards.
+ */
+ if (enic_sriov_enabled(enic) &&
+ enic->vf_type == ENIC_VF_TYPE_V2)
+ enic_admin_channel_close(enic);
+
enic_stop(enic->netdev);
+
enic_dev_soft_reset(enic);
enic_reset_addr_lists(enic);
enic_init_vnic_resources(enic);
enic_set_rss_nic_cfg(enic);
enic_dev_set_ig_vlan_rewrite_mode(enic);
enic_ext_cq(enic);
+
enic_open(enic->netdev);
+ /* Re-establish the admin/MBOX channel after the data path is back up,
+ * mirroring the SR-IOV enable path (channel open + mbox init). The
+ * channel was fully torn down by enic_admin_channel_close() above.
+ */
+ if (enic_sriov_enabled(enic) &&
+ enic->vf_type == ENIC_VF_TYPE_V2) {
+ if (enic_admin_channel_open(enic)) {
+ netdev_err(enic->netdev,
+ "admin channel reopen after reset failed\n");
+ } else {
+ enic_mbox_init(enic);
+ /* The link came back up during enic_open() above
+ * while MBOX sends were still disabled (channel not
+ * yet reopened), so that link-notify was dropped.
+ * Re-push current link state to registered VFs now.
+ */
+ schedule_work(&enic->link_notify_work);
+ }
+ }
+
/* Allow infiniband to fiddle with the device again */
enic_set_api_busy(enic, false);
@@ -2180,16 +2236,46 @@ static void enic_tx_hang_reset(struct work_struct *work)
/* Stop any activity from infiniband */
enic_set_api_busy(enic, true);
+ /* Fully tear down the V2 admin/MBOX channel before the hang reset, for
+ * the same reason as the soft reset path: stop the admin QP and free
+ * the admin resources before the hardware queues are wiped.
+ */
+ if (enic_sriov_enabled(enic) &&
+ enic->vf_type == ENIC_VF_TYPE_V2)
+ enic_admin_channel_close(enic);
+
enic_dev_hang_notify(enic);
enic_stop(enic->netdev);
+
enic_dev_hang_reset(enic);
enic_reset_addr_lists(enic);
enic_init_vnic_resources(enic);
enic_set_rss_nic_cfg(enic);
enic_dev_set_ig_vlan_rewrite_mode(enic);
enic_ext_cq(enic);
+
enic_open(enic->netdev);
+ /* Re-establish the admin/MBOX channel after the data path is back up,
+ * mirroring the SR-IOV enable path (channel open + mbox init). The
+ * channel was fully torn down by enic_admin_channel_close() above.
+ */
+ if (enic_sriov_enabled(enic) &&
+ enic->vf_type == ENIC_VF_TYPE_V2) {
+ if (enic_admin_channel_open(enic)) {
+ netdev_err(enic->netdev,
+ "admin channel reopen after reset failed\n");
+ } else {
+ enic_mbox_init(enic);
+ /* The link came back up during enic_open() above
+ * while MBOX sends were still disabled (channel not
+ * yet reopened), so that link-notify was dropped.
+ * Re-push current link state to registered VFs now.
+ */
+ schedule_work(&enic->link_notify_work);
+ }
+ }
+
/* Allow infiniband to fiddle with the device again */
enic_set_api_busy(enic, false);
@@ -2200,6 +2286,8 @@ static void enic_tx_hang_reset(struct work_struct *work)
static int enic_set_intr_mode(struct enic *enic)
{
+ unsigned int admin_reserve = enic->has_admin_channel ? 1 : 0;
+ unsigned int min_intr = ENIC_MSIX_MIN_INTR + admin_reserve;
unsigned int i;
int num_intr;
@@ -2210,12 +2298,12 @@ static int enic_set_intr_mode(struct enic *enic)
*/
if (enic->config.intr_mode < 1 &&
- enic->intr_avail >= ENIC_MSIX_MIN_INTR) {
+ enic->intr_avail >= min_intr) {
for (i = 0; i < enic->intr_avail; i++)
enic->msix_entry[i].entry = i;
num_intr = pci_enable_msix_range(enic->pdev, enic->msix_entry,
- ENIC_MSIX_MIN_INTR,
+ min_intr,
enic->intr_avail);
if (num_intr > 0) {
vnic_dev_set_intr_mode(enic->vdev,
@@ -2310,7 +2398,13 @@ static int enic_adjust_resources(struct enic *enic)
enic->cq_count = 2;
enic->intr_count = enic->intr_avail;
break;
- case VNIC_DEV_INTR_MODE_MSIX:
+ case VNIC_DEV_INTR_MODE_MSIX: {
+ /* Reserve one MSI-X slot for the admin channel interrupt
+ * when V2 SR-IOV admin channel resources are present.
+ */
+ unsigned int admin_reserve =
+ enic->has_admin_channel ? 1 : 0;
+
/* Adjust the number of wqs/rqs/cqs/interrupts that will be
* used based on which resource is the most constrained
*/
@@ -2319,7 +2413,8 @@ static int enic_adjust_resources(struct enic *enic)
ENIC_RQ_MIN_DEFAULT);
rq_avail = min3(enic->rq_avail, ENIC_RQ_MAX, rq_default);
max_queues = min(enic->cq_avail,
- enic->intr_avail - ENIC_MSIX_RESERVED_INTR);
+ enic->intr_avail - ENIC_MSIX_RESERVED_INTR -
+ admin_reserve);
if (wq_avail + rq_avail <= max_queues) {
enic->rq_count = rq_avail;
enic->wq_count = wq_avail;
@@ -2337,6 +2432,7 @@ static int enic_adjust_resources(struct enic *enic)
enic->intr_count = enic->cq_count + ENIC_MSIX_RESERVED_INTR;
break;
+ }
default:
dev_err(enic_get_dev(enic), "Unknown interrupt mode\n");
return -EINVAL;
@@ -2689,6 +2785,140 @@ static void enic_sriov_detect_vf_type(struct enic *enic)
enic->vf_type = ENIC_VF_TYPE_NONE;
}
}
+
+static int __maybe_unused
+enic_sriov_v2_enable(struct enic *enic, int num_vfs)
+{
+ int err;
+
+ if (!enic->has_admin_channel) {
+ netdev_err(enic->netdev,
+ "V2 SR-IOV requires admin channel resources\n");
+ return -EOPNOTSUPP;
+ }
+
+ enic->vf_state = kcalloc(num_vfs, sizeof(*enic->vf_state), GFP_KERNEL);
+ if (!enic->vf_state)
+ return -ENOMEM;
+
+ err = enic_admin_channel_open(enic);
+ if (err) {
+ netdev_err(enic->netdev,
+ "Failed to open admin channel: %d\n", err);
+ goto free_vf_state;
+ }
+
+ enic_mbox_init(enic);
+
+ enic->num_vfs = num_vfs;
+
+ err = pci_enable_sriov(enic->pdev, num_vfs);
+ if (err) {
+ netdev_err(enic->netdev,
+ "pci_enable_sriov failed: %d\n", err);
+ goto close_admin;
+ }
+
+ enic->priv_flags |= ENIC_SRIOV_ENABLED;
+ return num_vfs;
+
+close_admin:
+ enic->num_vfs = 0;
+ enic_admin_channel_close(enic);
+free_vf_state:
+ kfree(enic->vf_state);
+ enic->vf_state = NULL;
+ return err;
+}
+
+static void enic_sriov_v2_disable(struct enic *enic)
+{
+ /* Stop new VF link-state broadcasts before tearing down vf_state.
+ * Clearing ENIC_SRIOV_ENABLED makes enic_link_check() (called from
+ * the notify timer/ISR) skip the VF notify path, and cancelling
+ * link_notify_work ensures any already-queued broadcast has finished
+ * before vf_state is freed, closing a use-after-free window.
+ */
+ enic->priv_flags &= ~ENIC_SRIOV_ENABLED;
+ cancel_work_sync(&enic->link_notify_work);
+
+ pci_disable_sriov(enic->pdev);
+ enic_admin_channel_close(enic);
+ kfree(enic->vf_state);
+ enic->vf_state = NULL;
+ enic->num_vfs = 0;
+}
+
+/*
+ * enic_sriov_configure() and its V2 helpers are defined but not yet wired
+ * into enic_driver via .sriov_configure (see the __maybe_unused annotations);
+ * V2 enable/disable is activated in a follow-up series. Because the callback
+ * is not registered, it cannot run concurrently with the rtnl-protected reset
+ * paths (enic_reset(), enic_tx_hang_reset()) yet. Serialization against those
+ * paths is added together with the .sriov_configure wiring in that series.
+ */
+static int __maybe_unused
+enic_sriov_configure(struct pci_dev *pdev, int num_vfs)
+{
+ struct net_device *netdev = pci_get_drvdata(pdev);
+ struct enic *enic = netdev_priv(netdev);
+ struct enic_port_profile *pp;
+ int err;
+
+ if (num_vfs > 0) {
+ if (enic->config.mq_subvnic_count) {
+ netdev_err(netdev,
+ "SR-IOV not supported with multi-queue sub-vnics\n");
+ return -EOPNOTSUPP;
+ }
+
+ if (enic->vf_type == ENIC_VF_TYPE_NONE) {
+ netdev_err(netdev,
+ "SR-IOV not supported on this firmware version\n");
+ return -EOPNOTSUPP;
+ }
+
+ if (enic->vf_type == ENIC_VF_TYPE_V2)
+ return enic_sriov_v2_enable(enic, num_vfs);
+
+ pp = kcalloc(num_vfs, sizeof(*pp), GFP_KERNEL);
+ if (!pp)
+ return -ENOMEM;
+
+ err = pci_enable_sriov(pdev, num_vfs);
+ if (err) {
+ kfree(pp);
+ return err;
+ }
+
+ kfree(enic->pp);
+ enic->pp = pp;
+ enic->num_vfs = num_vfs;
+ enic->priv_flags |= ENIC_SRIOV_ENABLED;
+ return num_vfs;
+ }
+
+ if (!enic_sriov_enabled(enic))
+ return 0;
+
+ if (enic->vf_type == ENIC_VF_TYPE_V2) {
+ enic_sriov_v2_disable(enic);
+ return 0;
+ }
+
+ pp = kzalloc_obj(*enic->pp, GFP_KERNEL);
+ if (!pp)
+ return -ENOMEM;
+
+ pci_disable_sriov(pdev);
+ enic->num_vfs = 0;
+ enic->priv_flags &= ~ENIC_SRIOV_ENABLED;
+
+ kfree(enic->pp);
+ enic->pp = pp;
+
+ return 0;
+}
#endif
static int enic_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
@@ -2787,12 +3017,18 @@ static int enic_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
goto err_out_vnic_unregister;
#ifdef CONFIG_PCI_IOV
- /* Get number of subvnics */
+ enic_sriov_detect_vf_type(enic);
+
+ /* Auto-enable SR-IOV if VFs were pre-configured (e.g. at boot).
+ * V2 VFs require the admin channel, which is not yet set up at probe
+ * time; use sysfs (enic_sriov_configure) to enable V2 SR-IOV instead.
+ */
pos = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_SRIOV);
if (pos) {
pci_read_config_word(pdev, pos + PCI_SRIOV_TOTAL_VF,
&enic->num_vfs);
- if (enic->num_vfs) {
+ if (enic->num_vfs &&
+ enic->vf_type != ENIC_VF_TYPE_V2) {
err = pci_enable_sriov(pdev, enic->num_vfs);
if (err) {
dev_err(dev, "SRIOV enable failed, aborting."
@@ -2804,7 +3040,6 @@ static int enic_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
num_pps = enic->num_vfs;
}
}
- enic_sriov_detect_vf_type(enic);
#endif
/* Allocate structure for port profiles */
@@ -2881,6 +3116,7 @@ static int enic_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
INIT_WORK(&enic->reset, enic_reset);
INIT_WORK(&enic->tx_hang_reset, enic_tx_hang_reset);
INIT_WORK(&enic->change_mtu_work, enic_change_mtu_work);
+ INIT_WORK(&enic->link_notify_work, enic_link_notify_work_handler);
for (i = 0; i < enic->wq_count; i++)
spin_lock_init(&enic->wq[i].lock);
@@ -3034,14 +3270,16 @@ static void enic_remove(struct pci_dev *pdev)
cancel_work_sync(&enic->tx_hang_reset);
cancel_work_sync(&enic->change_mtu_work);
unregister_netdev(netdev);
- enic_dev_deinit(enic);
- vnic_dev_close(enic->vdev);
#ifdef CONFIG_PCI_IOV
if (enic_sriov_enabled(enic)) {
- pci_disable_sriov(pdev);
- enic->priv_flags &= ~ENIC_SRIOV_ENABLED;
+ if (enic->vf_type == ENIC_VF_TYPE_V2)
+ enic_sriov_v2_disable(enic);
+ else
+ pci_disable_sriov(pdev);
}
#endif
+ enic_dev_deinit(enic);
+ vnic_dev_close(enic->vdev);
kfree(enic->pp);
vnic_dev_unregister(enic->vdev);
enic_iounmap(enic);
diff --git a/drivers/net/ethernet/cisco/enic/enic_mbox.c b/drivers/net/ethernet/cisco/enic/enic_mbox.c
index 701972f90627..6fb6a1027bb6 100644
--- a/drivers/net/ethernet/cisco/enic/enic_mbox.c
+++ b/drivers/net/ethernet/cisco/enic/enic_mbox.c
@@ -624,8 +624,17 @@ int enic_mbox_vf_unregister(struct enic *enic)
void enic_mbox_init(struct enic *enic)
{
+ /* mbox_lock and mbox_comp must be initialized exactly once per
+ * device lifetime; the PF sriov_configure path can re-enter this
+ * on each enable cycle where these primitives are already set up.
+ */
+ if (!enic->mbox_initialized) {
+ mutex_init(&enic->mbox_lock);
+ init_completion(&enic->mbox_comp);
+ enic->mbox_initialized = true;
+ } else {
+ reinit_completion(&enic->mbox_comp);
+ }
enic->mbox_msg_num = 0;
- mutex_init(&enic->mbox_lock);
- init_completion(&enic->mbox_comp);
enic->admin_rq_handler = enic_mbox_recv_handler;
}
diff --git a/drivers/net/ethernet/cisco/enic/enic_pp.c b/drivers/net/ethernet/cisco/enic/enic_pp.c
index 4720a952725d..3f611e240c25 100644
--- a/drivers/net/ethernet/cisco/enic/enic_pp.c
+++ b/drivers/net/ethernet/cisco/enic/enic_pp.c
@@ -25,6 +25,11 @@ int enic_is_valid_pp_vf(struct enic *enic, int vf, int *err)
if (vf != PORT_SELF_VF) {
#ifdef CONFIG_PCI_IOV
if (enic_sriov_enabled(enic)) {
+ /* V2 SR-IOV uses MBOX, not port profiles */
+ if (enic->vf_type == ENIC_VF_TYPE_V2) {
+ *err = -EOPNOTSUPP;
+ goto err_out;
+ }
if (vf < 0 || vf >= enic->num_vfs) {
*err = -EINVAL;
goto err_out;
diff --git a/drivers/net/ethernet/cisco/enic/enic_res.c b/drivers/net/ethernet/cisco/enic/enic_res.c
index 2b7545d6a67f..436326ace049 100644
--- a/drivers/net/ethernet/cisco/enic/enic_res.c
+++ b/drivers/net/ethernet/cisco/enic/enic_res.c
@@ -59,6 +59,7 @@ int enic_get_vnic_config(struct enic *enic)
GET_CONFIG(intr_timer_usec);
GET_CONFIG(loop_tag);
GET_CONFIG(num_arfs);
+ GET_CONFIG(mq_subvnic_count);
GET_CONFIG(max_rq_ring);
GET_CONFIG(max_wq_ring);
GET_CONFIG(max_cq_ring);
diff --git a/drivers/net/ethernet/cisco/enic/vnic_enet.h b/drivers/net/ethernet/cisco/enic/vnic_enet.h
index 9e8e86262a3f..519d2969990b 100644
--- a/drivers/net/ethernet/cisco/enic/vnic_enet.h
+++ b/drivers/net/ethernet/cisco/enic/vnic_enet.h
@@ -21,7 +21,9 @@ struct vnic_enet_config {
u16 loop_tag;
u16 vf_rq_count;
u16 num_arfs;
- u8 reserved[66];
+ u8 reserved1[32];
+ u16 mq_subvnic_count;
+ u8 reserved2[32];
u32 max_rq_ring; // MAX RQ ring size
u32 max_wq_ring; // MAX WQ ring size
u32 max_cq_ring; // MAX CQ ring size
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v12 07/12] enic: add MBOX core send and receive for admin channel
From: Satish Kharat @ 2026-07-19 8:41 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni
Cc: netdev, linux-kernel, Breno Leitao, Sesidhar Baddela,
Satish Kharat
In-Reply-To: <20260719-enic-sriov-v2-admin-channel-v2-v12-0-6ff2c617001d@cisco.com>
Implement the mailbox protocol engine used for PF-VF communication
over the admin channel.
The send path (enic_mbox_send_msg) builds a message with a common
header, DMA-maps it, posts a single WQ descriptor with the
destination vnic ID encoded in the VLAN tag field, and polls
the WQ CQ for completion.
MBOX sends are gated by enic->mbox_send_disabled: enic_mbox_send_msg()
returns early while it is set. It is set at the very start of both
enic_admin_channel_open() and enic_admin_channel_close(), and is
cleared in enic_admin_channel_open() only once the admin WQ/RQ/CQ and
interrupt are fully allocated, programmed and enabled. Keeping it set
for the whole open sequence means an early failure that returns before
the channel is ready (as well as a not-yet-ready or torn-down channel)
leaves sends disabled, so a concurrent sender can never race an MBOX
send against a half-open or freed admin_wq.
The receive path (enic_mbox_recv_handler) is installed as the admin
RQ callback and validates incoming message headers. PF/VF-specific
dispatch will be added in subsequent commits.
Signed-off-by: Satish Kharat <satishkh@cisco.com>
---
drivers/net/ethernet/cisco/enic/Makefile | 2 +-
drivers/net/ethernet/cisco/enic/enic.h | 6 +
drivers/net/ethernet/cisco/enic/enic_admin.c | 42 ++++++-
drivers/net/ethernet/cisco/enic/enic_mbox.c | 170 +++++++++++++++++++++++++++
drivers/net/ethernet/cisco/enic/enic_mbox.h | 8 ++
5 files changed, 225 insertions(+), 3 deletions(-)
diff --git a/drivers/net/ethernet/cisco/enic/Makefile b/drivers/net/ethernet/cisco/enic/Makefile
index 7ae72fefc99a..e38aaf34c148 100644
--- a/drivers/net/ethernet/cisco/enic/Makefile
+++ b/drivers/net/ethernet/cisco/enic/Makefile
@@ -4,5 +4,5 @@ obj-$(CONFIG_ENIC) := enic.o
enic-y := enic_main.o vnic_cq.o vnic_intr.o vnic_wq.o \
enic_res.o enic_dev.o enic_pp.o vnic_dev.o vnic_rq.o vnic_vic.o \
enic_ethtool.o enic_api.o enic_clsf.o enic_rq.o enic_wq.o \
- enic_admin.o
+ enic_admin.o enic_mbox.o
diff --git a/drivers/net/ethernet/cisco/enic/enic.h b/drivers/net/ethernet/cisco/enic/enic.h
index 401123e6df1d..b009d87da4bd 100644
--- a/drivers/net/ethernet/cisco/enic/enic.h
+++ b/drivers/net/ethernet/cisco/enic/enic.h
@@ -297,6 +297,8 @@ struct enic {
* left the resources freed.
*/
bool admin_chan_up;
+ /* set on send timeout; cleared on channel re-open */
+ bool mbox_send_disabled;
struct vnic_wq admin_wq;
struct vnic_rq admin_rq;
struct vnic_cq admin_cq[2];
@@ -309,6 +311,10 @@ struct enic {
unsigned int admin_msg_count; /* current depth of admin_msg_list */
void (*admin_rq_handler)(struct enic *enic, void *buf,
unsigned int len);
+
+ /* MBOX protocol state — mbox_lock serializes admin WQ sends */
+ struct mutex mbox_lock;
+ u64 mbox_msg_num;
};
static inline struct net_device *vnic_get_netdev(struct vnic_dev *vdev)
diff --git a/drivers/net/ethernet/cisco/enic/enic_admin.c b/drivers/net/ethernet/cisco/enic/enic_admin.c
index 9358165263d9..3e7103f2afc1 100644
--- a/drivers/net/ethernet/cisco/enic/enic_admin.c
+++ b/drivers/net/ethernet/cisco/enic/enic_admin.c
@@ -19,6 +19,7 @@
#include "cq_enet_desc.h"
#include "wq_enet_desc.h"
#include "rq_enet_desc.h"
+#include "enic_mbox.h"
/* Clean up any admin WQ buffers still held by hardware at close time.
* Normally buffers are freed inline after send completion, but a timed-out
@@ -213,7 +214,26 @@ unsigned int enic_admin_rq_cq_service(struct enic *enic)
goto next_desc;
}
- enic_admin_msg_enqueue(enic, buf->os_buf, bytes_written);
+ if (enic->admin_rq_handler) {
+ u16 sender_vlan;
+
+ /* Firmware sets the CQ VLAN field to identify the
+ * sender: 0 = PF, 1-based = VF index. Overwrite
+ * the untrusted src_vnic_id in the MBOX header with
+ * the hardware-verified value.
+ */
+ sender_vlan = le16_to_cpu(rq_desc->vlan);
+ if (bytes_written >= sizeof(struct enic_mbox_hdr)) {
+ struct enic_mbox_hdr *hdr = buf->os_buf;
+
+ hdr->src_vnic_id = (sender_vlan == 0) ?
+ cpu_to_le16(ENIC_MBOX_DST_PF) :
+ cpu_to_le16(sender_vlan - 1);
+ }
+
+ enic_admin_msg_enqueue(enic, buf->os_buf,
+ bytes_written);
+ }
next_desc:
enic_admin_rq_buf_clean(rq, rq->to_clean);
@@ -468,8 +488,9 @@ static void enic_admin_init_resources(struct enic *enic)
VNIC_CQ_MSG_DISABLE,
intr_offset,
0 /* cq_message_addr */);
+ /* coalescing_timer, coalescing_type, mask_on_assertion */
vnic_intr_init(&enic->admin_intr,
- 0, 0, 1); /* coalescing_timer, coalescing_type, mask_on_assertion */
+ 0, 0, 1);
}
static void enic_admin_msg_drain(struct enic *enic)
@@ -492,6 +513,13 @@ int enic_admin_channel_open(struct enic *enic)
if (!enic->has_admin_channel)
return -ENODEV;
+ /* Keep MBOX sends disabled for the entire open sequence. It is
+ * cleared only after every resource is allocated and enabled below,
+ * so any early error return here leaves sends disabled and a
+ * concurrent sender cannot touch a half-open or freed admin_wq.
+ */
+ WRITE_ONCE(enic->mbox_send_disabled, true);
+
err = enic_admin_alloc_resources(enic);
if (err) {
netdev_err(enic->netdev,
@@ -534,6 +562,14 @@ int enic_admin_channel_open(struct enic *enic)
vnic_intr_unmask(&enic->admin_intr);
+ /* Only now that the admin WQ/RQ/CQ and interrupt are fully allocated,
+ * programmed and enabled is it safe to allow MBOX sends. Clearing this
+ * earlier opened a window where a concurrent sender (e.g. link-notify
+ * work scheduled by a post-reset link-up) could call enic_mbox_send_msg()
+ * against a not-yet-allocated admin_wq and crash.
+ */
+ WRITE_ONCE(enic->mbox_send_disabled, false);
+
netdev_dbg(enic->netdev,
"admin channel open: intr=%u wq_avail=%u rq_avail=%u cq0_color=%u cq1_color=%u\n",
enic->admin_intr_index,
@@ -575,6 +611,8 @@ void enic_admin_channel_close(struct enic *enic)
if (!enic->admin_chan_up)
return;
+ WRITE_ONCE(enic->mbox_send_disabled, true);
+
netdev_dbg(enic->netdev, "admin channel close\n");
vnic_intr_mask(&enic->admin_intr);
diff --git a/drivers/net/ethernet/cisco/enic/enic_mbox.c b/drivers/net/ethernet/cisco/enic/enic_mbox.c
new file mode 100644
index 000000000000..3709704bee02
--- /dev/null
+++ b/drivers/net/ethernet/cisco/enic/enic_mbox.c
@@ -0,0 +1,170 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright 2025 Cisco Systems, Inc. All rights reserved.
+
+#include <linux/kernel.h>
+#include <linux/netdevice.h>
+#include <linux/dma-mapping.h>
+#include <linux/delay.h>
+
+#include "vnic_dev.h"
+#include "vnic_wq.h"
+#include "vnic_cq.h"
+#include "enic.h"
+#include "enic_admin.h"
+#include "enic_mbox.h"
+#include "wq_enet_desc.h"
+
+#define ENIC_MBOX_POLL_TIMEOUT_US 5000000
+#define ENIC_MBOX_POLL_INTERVAL_US 100
+
+static void enic_mbox_fill_hdr(struct enic *enic, struct enic_mbox_hdr *hdr,
+ u8 msg_type, u16 dst_vnic_id, u16 msg_len)
+{
+ memset(hdr, 0, sizeof(*hdr));
+ hdr->dst_vnic_id = cpu_to_le16(dst_vnic_id);
+ hdr->msg_type = msg_type;
+ hdr->msg_len = cpu_to_le16(msg_len);
+ hdr->msg_num = cpu_to_le64(++enic->mbox_msg_num);
+}
+
+int enic_mbox_send_msg(struct enic *enic, u8 msg_type, u16 dst_vnic_id,
+ void *payload, u16 payload_len)
+{
+ u16 total_len = sizeof(struct enic_mbox_hdr) + payload_len;
+ struct vnic_wq *wq = &enic->admin_wq;
+ struct wq_enet_desc *desc;
+ unsigned long timeout;
+ dma_addr_t dma_addr;
+ u16 vlan_tag;
+ void *buf;
+ int err;
+
+ /* Serialize MBOX sends. The admin channel is a low-frequency
+ * control path; holding the mutex across the poll is acceptable.
+ */
+ mutex_lock(&enic->mbox_lock);
+
+ if (!enic->has_admin_channel || READ_ONCE(enic->mbox_send_disabled)) {
+ err = -ENODEV;
+ goto unlock;
+ }
+
+ if (vnic_wq_desc_avail(wq) == 0) {
+ err = -ENOSPC;
+ goto unlock;
+ }
+
+ buf = kmalloc(total_len, GFP_KERNEL);
+ if (!buf) {
+ err = -ENOMEM;
+ goto unlock;
+ }
+
+ enic_mbox_fill_hdr(enic, buf, msg_type, dst_vnic_id, total_len);
+ if (payload_len) {
+ void *dst = buf + sizeof(struct enic_mbox_hdr);
+
+ memcpy(dst, payload, payload_len);
+ }
+
+ dma_addr = dma_map_single(&enic->pdev->dev, buf, total_len,
+ DMA_TO_DEVICE);
+ if (dma_mapping_error(&enic->pdev->dev, dma_addr)) {
+ kfree(buf);
+ err = -ENOMEM;
+ goto unlock;
+ }
+
+ /* Firmware uses vlan field for routing: 0 = PF, 1-based = VF index */
+ if (dst_vnic_id == ENIC_MBOX_DST_PF)
+ vlan_tag = 0;
+ else
+ vlan_tag = dst_vnic_id + 1;
+
+ desc = vnic_wq_next_desc(wq);
+ wq_enet_desc_enc(desc, (u64)dma_addr | VNIC_PADDR_TARGET,
+ total_len,
+ 0, 0, 0, /* mss, hdr_len, offload_mode */
+ 1, 1, /* eop, cq_entry */
+ 0, /* fcoe_encap */
+ 1, vlan_tag, /* vlan_tag_insert, vlan_tag */
+ 0); /* loopback */
+ vnic_wq_post(wq, buf, dma_addr, total_len,
+ 1, 1, /* sop, eop */
+ 1, 1, /* desc_skip_cnt, cq_entry */
+ 0, 0); /* compressed_send, wrid */
+ vnic_wq_doorbell(wq);
+
+ timeout = jiffies + usecs_to_jiffies(ENIC_MBOX_POLL_TIMEOUT_US);
+ err = -ETIMEDOUT;
+ while (time_before(jiffies, timeout)) {
+ if (enic_admin_wq_cq_service(enic)) {
+ err = 0;
+ break;
+ }
+ usleep_range(ENIC_MBOX_POLL_INTERVAL_US,
+ ENIC_MBOX_POLL_INTERVAL_US + 50);
+ }
+ /* Final check in case completion arrived during the last sleep */
+ if (err && enic_admin_wq_cq_service(enic))
+ err = 0;
+
+ if (!err) {
+ wq->to_clean = wq->to_clean->next;
+ wq->ring.desc_avail++;
+ dma_unmap_single(&enic->pdev->dev, dma_addr, total_len,
+ DMA_TO_DEVICE);
+ kfree(buf);
+ } else {
+ netdev_err(enic->netdev,
+ "MBOX send timed out (type %u dst %u), disabling channel\n",
+ msg_type, dst_vnic_id);
+ /*
+ * The WQ descriptor is still live in hardware. Do not unmap
+ * or free the buffer: the device may still DMA from dma_addr.
+ * Mark the channel unusable so no further sends are attempted.
+ */
+ WRITE_ONCE(enic->mbox_send_disabled, true);
+ }
+
+ netdev_dbg(enic->netdev,
+ "MBOX send msg_type %u dst %u vlan %u err %d\n",
+ msg_type, dst_vnic_id, vlan_tag, err);
+unlock:
+ mutex_unlock(&enic->mbox_lock);
+ return err;
+}
+
+static void enic_mbox_recv_handler(struct enic *enic, void *buf,
+ unsigned int len)
+{
+ struct enic_mbox_hdr *hdr = buf;
+
+ if (len < sizeof(*hdr)) {
+ if (net_ratelimit())
+ netdev_warn(enic->netdev,
+ "MBOX: truncated message (len %u < %zu)\n",
+ len, sizeof(*hdr));
+ return;
+ }
+
+ if (hdr->msg_type >= ENIC_MBOX_MAX) {
+ if (net_ratelimit())
+ netdev_warn(enic->netdev,
+ "MBOX: unknown msg type %u\n",
+ hdr->msg_type);
+ return;
+ }
+
+ netdev_dbg(enic->netdev,
+ "MBOX recv: type %u from vnic %u len %u\n",
+ hdr->msg_type, le16_to_cpu(hdr->src_vnic_id),
+ le16_to_cpu(hdr->msg_len));
+}
+
+void enic_mbox_init(struct enic *enic)
+{
+ enic->mbox_msg_num = 0;
+ mutex_init(&enic->mbox_lock);
+ enic->admin_rq_handler = enic_mbox_recv_handler;
+}
diff --git a/drivers/net/ethernet/cisco/enic/enic_mbox.h b/drivers/net/ethernet/cisco/enic/enic_mbox.h
index a52f1d25cb21..73fd7f783ee2 100644
--- a/drivers/net/ethernet/cisco/enic/enic_mbox.h
+++ b/drivers/net/ethernet/cisco/enic/enic_mbox.h
@@ -80,4 +80,12 @@ struct enic_mbox_pf_link_state_ack_msg {
struct enic_mbox_generic_reply ack;
};
+#define ENIC_MBOX_DST_PF 0xFFFF
+
+struct enic;
+
+void enic_mbox_init(struct enic *enic);
+int enic_mbox_send_msg(struct enic *enic, u8 msg_type, u16 dst_vnic_id,
+ void *payload, u16 payload_len);
+
#endif /* _ENIC_MBOX_H_ */
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v12 12/12] enic: re-establish V2 VF admin channel and PF registration after reset
From: Satish Kharat @ 2026-07-19 8:41 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni
Cc: netdev, linux-kernel, Breno Leitao, Sesidhar Baddela,
Satish Kharat
In-Reply-To: <20260719-enic-sriov-v2-admin-channel-v2-v12-0-6ff2c617001d@cisco.com>
The reset paths (enic_reset/enic_tx_hang_reset) tore down and re-opened
the V2 admin/MBOX channel only for the PF: the close/reopen was gated on
enic_sriov_enabled() && vf_type == ENIC_VF_TYPE_V2, which is never true on
a VF (vf_type is set only on the PF; VFs are identified by
enic_is_sriov_vf_v2()). A VF-initiated reset therefore left the VF admin
QP wiped by the reset but never re-opened, and the VF never re-registered
with the PF, so VF<->PF MBOX traffic (link state, MAC, packet filter)
stopped working until the VF was re-probed.
Factor the decision into enic_has_admin_chan() (true for a V2 PF while
SR-IOV is enabled and for every V2 VF) and the reopen sequence into
enic_admin_chan_reopen(). For a VF the helper additionally re-runs the
probe-time handshake (enic_mbox_vf_capability_check() +
enic_mbox_vf_register()) so the PF learns about the VF again; for a PF it
re-pushes the current link state as before.
Signed-off-by: Satish Kharat <satishkh@cisco.com>
---
drivers/net/ethernet/cisco/enic/enic_main.c | 105 +++++++++++++++++-----------
1 file changed, 65 insertions(+), 40 deletions(-)
diff --git a/drivers/net/ethernet/cisco/enic/enic_main.c b/drivers/net/ethernet/cisco/enic/enic_main.c
index 99c06a049800..be3ae0a57af3 100644
--- a/drivers/net/ethernet/cisco/enic/enic_main.c
+++ b/drivers/net/ethernet/cisco/enic/enic_main.c
@@ -2171,6 +2171,57 @@ static void enic_set_api_busy(struct enic *enic, bool busy)
spin_unlock(&enic->enic_api_lock);
}
+/* The admin/MBOX channel exists on a V2 PF while SR-IOV is enabled and on
+ * every V2 VF. A reset wipes the admin WQ/RQ/CQ, so such devices must tear
+ * the channel down before the reset and re-establish it afterwards.
+ */
+static bool enic_has_admin_chan(struct enic *enic)
+{
+ return enic_is_sriov_vf_v2(enic) ||
+ (enic_sriov_enabled(enic) && enic->vf_type == ENIC_VF_TYPE_V2);
+}
+
+/* Re-establish the admin/MBOX channel after a reset has re-created the data
+ * path. Mirrors the relevant part of the probe / SR-IOV-enable sequence:
+ * reopen the channel and reinitialise MBOX, then for a VF re-run the PF
+ * handshake (its admin QP and PF-side registration were torn down by the
+ * reset), or for a PF re-push the current link state to registered VFs.
+ */
+static void enic_admin_chan_reopen(struct enic *enic)
+{
+ int err;
+
+ err = enic_admin_channel_open(enic);
+ if (err) {
+ netdev_err(enic->netdev,
+ "admin channel reopen after reset failed: %d\n", err);
+ return;
+ }
+
+ enic_mbox_init(enic);
+
+ if (enic_is_sriov_vf_v2(enic)) {
+ err = enic_mbox_vf_capability_check(enic);
+ if (err) {
+ netdev_err(enic->netdev,
+ "MBOX capability check after reset failed: %d\n",
+ err);
+ return;
+ }
+ err = enic_mbox_vf_register(enic);
+ if (err)
+ netdev_err(enic->netdev,
+ "MBOX VF re-registration after reset failed: %d\n",
+ err);
+ } else {
+ /* The link came back up during enic_open() above while MBOX
+ * sends were still disabled (channel not yet reopened), so that
+ * link-notify was dropped. Re-push current link state now.
+ */
+ schedule_work(&enic->link_notify_work);
+ }
+}
+
static void enic_reset(struct work_struct *work)
{
struct enic *enic = container_of(work, struct enic, reset);
@@ -2189,8 +2240,7 @@ static void enic_reset(struct work_struct *work)
* DMAs from the about-to-be-reset rings) and frees the admin resources
* so they are cleanly re-allocated afterwards.
*/
- if (enic_sriov_enabled(enic) &&
- enic->vf_type == ENIC_VF_TYPE_V2)
+ if (enic_has_admin_chan(enic))
enic_admin_channel_close(enic);
enic_stop(enic->netdev);
@@ -2204,25 +2254,13 @@ static void enic_reset(struct work_struct *work)
enic_open(enic->netdev);
- /* Re-establish the admin/MBOX channel after the data path is back up,
- * mirroring the SR-IOV enable path (channel open + mbox init). The
- * channel was fully torn down by enic_admin_channel_close() above.
+ /* Re-establish the admin/MBOX channel after the data path is back up.
+ * It was fully torn down by enic_admin_channel_close() above;
+ * enic_admin_chan_reopen() reopens it and, for a PF re-pushes link
+ * state, or for a VF re-runs the probe-time PF handshake.
*/
- if (enic_sriov_enabled(enic) &&
- enic->vf_type == ENIC_VF_TYPE_V2) {
- if (enic_admin_channel_open(enic)) {
- netdev_err(enic->netdev,
- "admin channel reopen after reset failed\n");
- } else {
- enic_mbox_init(enic);
- /* The link came back up during enic_open() above
- * while MBOX sends were still disabled (channel not
- * yet reopened), so that link-notify was dropped.
- * Re-push current link state to registered VFs now.
- */
- schedule_work(&enic->link_notify_work);
- }
- }
+ if (enic_has_admin_chan(enic))
+ enic_admin_chan_reopen(enic);
/* Allow infiniband to fiddle with the device again */
enic_set_api_busy(enic, false);
@@ -2245,8 +2283,7 @@ static void enic_tx_hang_reset(struct work_struct *work)
* the same reason as the soft reset path: stop the admin QP and free
* the admin resources before the hardware queues are wiped.
*/
- if (enic_sriov_enabled(enic) &&
- enic->vf_type == ENIC_VF_TYPE_V2)
+ if (enic_has_admin_chan(enic))
enic_admin_channel_close(enic);
enic_dev_hang_notify(enic);
@@ -2261,25 +2298,13 @@ static void enic_tx_hang_reset(struct work_struct *work)
enic_open(enic->netdev);
- /* Re-establish the admin/MBOX channel after the data path is back up,
- * mirroring the SR-IOV enable path (channel open + mbox init). The
- * channel was fully torn down by enic_admin_channel_close() above.
+ /* Re-establish the admin/MBOX channel after the data path is back up.
+ * It was fully torn down by enic_admin_channel_close() above;
+ * enic_admin_chan_reopen() reopens it and, for a PF re-pushes link
+ * state, or for a VF re-runs the probe-time PF handshake.
*/
- if (enic_sriov_enabled(enic) &&
- enic->vf_type == ENIC_VF_TYPE_V2) {
- if (enic_admin_channel_open(enic)) {
- netdev_err(enic->netdev,
- "admin channel reopen after reset failed\n");
- } else {
- enic_mbox_init(enic);
- /* The link came back up during enic_open() above
- * while MBOX sends were still disabled (channel not
- * yet reopened), so that link-notify was dropped.
- * Re-push current link state to registered VFs now.
- */
- schedule_work(&enic->link_notify_work);
- }
- }
+ if (enic_has_admin_chan(enic))
+ enic_admin_chan_reopen(enic);
/* Allow infiniband to fiddle with the device again */
enic_set_api_busy(enic, false);
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v12 04/12] enic: add admin RQ buffer management
From: Satish Kharat @ 2026-07-19 8:41 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni
Cc: netdev, linux-kernel, Breno Leitao, Sesidhar Baddela,
Satish Kharat
In-Reply-To: <20260719-enic-sriov-v2-admin-channel-v2-v12-0-6ff2c617001d@cisco.com>
The admin receive queue needs pre-posted DMA buffers for incoming
mailbox messages from VFs. Each buffer is a kmalloc'd region mapped
for DMA (2048 bytes, sufficient for any MBOX message).
Add enic_admin_rq_fill(gfp) to post buffers at open time, and
enic_admin_rq_drain() to unmap and free them at close time.
Wire both into the admin channel open/close paths. The gfp_t
parameter lets the caller pass the allocation context; both current
callers -- channel open and the CQ-poll work handler that refills
after draining (added in the next patch) -- run in process context
and use GFP_KERNEL.
Signed-off-by: Satish Kharat <satishkh@cisco.com>
---
drivers/net/ethernet/cisco/enic/enic_admin.c | 66 +++++++++++++++++++++++++++-
1 file changed, 64 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/cisco/enic/enic_admin.c b/drivers/net/ethernet/cisco/enic/enic_admin.c
index 50b46b92c88f..b2be42092106 100644
--- a/drivers/net/ethernet/cisco/enic/enic_admin.c
+++ b/drivers/net/ethernet/cisco/enic/enic_admin.c
@@ -3,6 +3,7 @@
#include <linux/kernel.h>
#include <linux/netdevice.h>
+#include <linux/dma-mapping.h>
#include "vnic_dev.h"
#include "vnic_wq.h"
@@ -34,10 +35,63 @@ static void enic_admin_wq_buf_clean(struct vnic_wq *wq,
}
}
-/* No-op: admin RQ buffer teardown is handled in enic_admin_channel_close */
static void enic_admin_rq_buf_clean(struct vnic_rq *rq,
struct vnic_rq_buf *buf)
{
+ struct enic *enic = vnic_dev_priv(rq->vdev);
+
+ if (!buf->os_buf)
+ return;
+
+ dma_unmap_single(&enic->pdev->dev, buf->dma_addr, buf->len,
+ DMA_FROM_DEVICE);
+ kfree(buf->os_buf);
+ buf->os_buf = NULL;
+}
+
+static int enic_admin_rq_post_one(struct enic *enic, gfp_t gfp)
+{
+ struct vnic_rq *rq = &enic->admin_rq;
+ struct rq_enet_desc *desc;
+ dma_addr_t dma_addr;
+ void *buf;
+
+ buf = kmalloc(ENIC_ADMIN_BUF_SIZE, gfp);
+ if (!buf)
+ return -ENOMEM;
+
+ dma_addr = dma_map_single(&enic->pdev->dev, buf, ENIC_ADMIN_BUF_SIZE,
+ DMA_FROM_DEVICE);
+ if (dma_mapping_error(&enic->pdev->dev, dma_addr)) {
+ kfree(buf);
+ return -ENOMEM;
+ }
+
+ desc = vnic_rq_next_desc(rq);
+ rq_enet_desc_enc(desc, (u64)dma_addr | VNIC_PADDR_TARGET,
+ RQ_ENET_TYPE_ONLY_SOP, ENIC_ADMIN_BUF_SIZE);
+ vnic_rq_post(rq, buf, 0, dma_addr, ENIC_ADMIN_BUF_SIZE, 0);
+
+ return 0;
+}
+
+static int enic_admin_rq_fill(struct enic *enic, gfp_t gfp)
+{
+ struct vnic_rq *rq = &enic->admin_rq;
+ int err;
+
+ while (vnic_rq_desc_avail(rq) > 0) {
+ err = enic_admin_rq_post_one(enic, gfp);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+static void enic_admin_rq_drain(struct enic *enic)
+{
+ vnic_rq_clean(&enic->admin_rq, enic_admin_rq_buf_clean);
}
static int enic_admin_qp_type_set(struct enic *enic, u32 enable)
@@ -171,6 +225,13 @@ int enic_admin_channel_open(struct enic *enic)
vnic_wq_enable(&enic->admin_wq);
vnic_rq_enable(&enic->admin_rq);
+ err = enic_admin_rq_fill(enic, GFP_KERNEL);
+ if (err) {
+ netdev_err(enic->netdev,
+ "Failed to fill admin RQ buffers: %d\n", err);
+ goto disable_queues;
+ }
+
err = enic_admin_qp_type_set(enic, QP_ENABLE);
if (err) {
netdev_err(enic->netdev,
@@ -188,6 +249,7 @@ int enic_admin_channel_open(struct enic *enic)
netdev_warn(enic->netdev, "Failed to disable admin WQ\n");
if (vnic_rq_disable(&enic->admin_rq))
netdev_warn(enic->netdev, "Failed to disable admin RQ\n");
+ enic_admin_rq_drain(enic);
enic_admin_free_resources(enic);
return err;
}
@@ -218,7 +280,7 @@ void enic_admin_channel_close(struct enic *enic)
"Failed to disable admin RQ: %d\n", err);
vnic_wq_clean(&enic->admin_wq, enic_admin_wq_buf_clean);
- vnic_rq_clean(&enic->admin_rq, enic_admin_rq_buf_clean);
+ enic_admin_rq_drain(enic);
vnic_cq_clean(&enic->admin_cq[0]);
vnic_cq_clean(&enic->admin_cq[1]);
enic_admin_free_resources(enic);
--
2.43.0
^ permalink raw reply related
* [PATCH net-next v12 00/12] enic: SR-IOV V2 admin channel and MBOX protocol
From: Satish Kharat @ 2026-07-19 8:41 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni
Cc: netdev, linux-kernel, Breno Leitao, Sesidhar Baddela,
Satish Kharat
This series adds the admin channel infrastructure and mailbox (MBOX)
protocol needed for V2 SR-IOV support in the enic driver.
The V2 SR-IOV design uses a direct PF-VF communication channel built on
dedicated WQ/RQ/CQ hardware resources and an MSI-X interrupt.
Patch 1 is an independent fix for a pre-existing use-after-free in
enic_remove() (the tx_hang_reset work item was never cancelled on
removal). It is unrelated to SR-IOV but lives in the same teardown
path the later patches touch, so it is carried at the head of the
series.
Firmware capability and admin channel infrastructure (patches 2-5):
- Probe-time firmware feature check for V2 SR-IOV support
- Admin channel open/close, RQ buffer management, CQ service
with MSI-X interrupt and workqueue-based polling
MBOX protocol and VF enable (patches 6-11):
- MBOX message types, core send/receive, PF and VF handlers
- V2 SR-IOV enable wiring with admin channel setup
- V2 VF probe with admin channel and PF registration
Patch 12 completes reset recovery for V2 VFs: the reset paths added
earlier in the series re-establish the admin channel only for the PF,
which left a VF unregistered and unable to exchange MBOX traffic after
a reset taken on the VF.
Signed-off-by: Satish Kharat <satishkh@cisco.com>
---
Changes in v12:
- Rebased on net-next.
- patch 2: reworded the commit message for accuracy (no code change) and
added Reviewed-by: Breno Leitao.
- patch 5: return the actual admin CQ interrupt credit count instead of a
forced minimum of one, and correct the accompanying comment; the MSI-X
vector is re-armed by the unmask bit independently of the credit value,
so there is no functional change.
- Link to v11: https://patch.msgid.link/20260703-enic-sriov-v2-admin-channel-v2-v11-0-5b739f1fe9e5@cisco.com
Changes in v11:
- Set mbox_send_disabled at the start of enic_admin_channel_open() so an
early error return cannot leave MBOX sends enabled against a half-open
or freed admin_wq (patch 7)
- Document that enic_sriov_configure() is not yet wired (__maybe_unused)
and that rtnl serialization vs the reset paths is added when it is
wired in a follow-up (patch 10)
- Document the admin CQ poll-handler credit return: the pre-drain credit
snapshot and the minimum one-credit unmask that re-arms the MSI-X
vector; no functional change (patch 5)
- Improve the VF capability-check log so a PF rejection is not reported
as "version too old" (patch 9)
- Clarify the enic_remove() teardown comment and note in the commit
message that a V2 VF without admin WQ/RQ/CQ resources fails probe with
-ENODEV (patch 11)
- Link to v10: https://patch.msgid.link/20260629-enic-sriov-v2-admin-channel-v2-v10-0-62569af83417@cisco.com
Changes in v10:
- Cancel tx_hang_reset work in enic_remove() to fix a pre-existing
use-after-free when a TX timeout fires during device removal; carried
as an independent fix at the head of the series (new patch 1)
[Sashiko]
- Clarify in the patch 2 commit message that V2 VFs are only enabled via
.sriov_configure, which rejects firmware without V2 support, so such
firmware never exposes VFs (patch 2) [Sashiko]
- Track admin-channel up/down state and gate admin/MBOX operations on
it, fixing a NULL pointer dereference when close() runs after a failed
open() and when a reset fails to reopen the channel (patch 3)
[Sashiko]
- Bound the admin message list with ENIC_ADMIN_MSG_MAX (256) to prevent
a malicious VF from exhausting PF memory (patch 5) [Sashiko]
- Name the admin MSI-X interrupt with pci_name() instead of the
not-yet-registered netdev name so it no longer appears as
"eth%d-admin" in /proc/interrupts (patch 5) [Sashiko]
- Document the in-order admin CQ/RQ completion guarantee in a comment
(patch 5) [Sashiko]
- On a VF, validate that admin MBOX messages are sourced from the PF
before acting on them, rejecting spoofed link-state messages (patch 9)
[Sashiko]
- Move the link_notify_work initialisation ahead of the VF setup block
so a VF probe error path cannot cancel_work_sync() an uninitialised
work item (patch 11) [Sashiko]
- Cancel link_notify_work in enic_remove() after unregister_netdev() to
close the narrow window where enic_link_check() could schedule it just
as SR-IOV was disabled, leaving the work to outlive vf_state (patch 11)
[Sashiko]
- Re-establish the V2 VF admin channel and re-run PF registration after
a driver-initiated device reset (the soft reset from a WQ/RQ error and
the tx-hang reset from a TX timeout); previously only the PF recovered,
so a reset taken on a VF left it unable to exchange MBOX traffic
(new patch 12) [Sashiko]
Testing:
- Exercised on a Cisco VIC with multiple V2 VFs under a KASAN + lockdep
+ DMA-API-debug kernel. VF resets (soft and tx-hang) and PF reset,
including a 10x reset stress loop, re-established the admin channel and
re-registered the VFs with no use-after-free, lockdep, or DMA-API
warnings. MBOX control-plane operations (VF MAC/VLAN/spoofchk/trust/
MTU) were verified to survive resets.
- Link to v9: https://patch.msgid.link/20260617-enic-sriov-v2-admin-channel-v2-v9-0-37f5f5af4c93@cisco.com
Changes in v9:
- Use dma_rmb() instead of rmb() when reading admin RQ completion
descriptors written by DMA (patch 4) [Sashiko]
- Use GFP_KERNEL instead of GFP_ATOMIC for admin RQ refill and for
received-message allocation; both run in workqueue (process)
context after the v8 NAPI-to-workqueue switch (patch 4) [Sashiko]
- Correct the enic_admin_msg comment to describe the workqueue
enqueue path rather than NAPI (patch 4) [Sashiko]
- Set mbox_send_disabled in enic_admin_channel_close() so a MBOX
send cannot race with channel teardown (patch 6) [Sashiko]
- Send the actual PF carrier state to a VF on registration instead
of unconditionally reporting link up (patch 7) [Sashiko]
- Call reinit_completion() before setting mbox_expected_reply so a
reply arriving between the two is not missed (patch 8) [Sashiko]
- Defer PF->VF link state notification to a workqueue and gate it on
carrier transitions; enic_link_check() runs in the notify (atomic)
context while the MBOX send sleeps on a mutex/completion (patch 9)
[Sashiko]
- Clear ENIC_SRIOV_ENABLED and cancel the link-notify work before
freeing per-VF state in the SR-IOV disable path, closing a
use-after-free window against a concurrent link notification
(patch 9) [Sashiko]
- Link to v8: https://patch.msgid.link/20260609-enic-sriov-v2-admin-channel-v2-v8-0-8ad8babbb826@cisco.com
Changes in v8:
- Replace NAPI polling with workqueue for admin CQ service — admin
channel is low-frequency control traffic, not data path (patch 4)
[Jakub Kicinski]
- Use explicit enum value (= 4) for VIC_FEATURE_SRIOV instead of
placeholder VIC_FEATURE_PTP entry (patch 1) [Breno Leitao]
- Remove unnecessary rmb() in WQ CQ service (patch 4) [Jakub Kicinski]
- Remove admin_msg_drop_cnt counter (patch 4) [Simon Horman]
- Drop NAPI reschedule on RQ refill failure — the NAPI-to-workqueue
switch removes the livelock and budget issues (patch 4) [Simon Horman]
- Remove unnecessary READ_ONCE/WRITE_ONCE on admin_rq_handler — all
access is serialized by probe/remove (patch 6) [Jakub Kicinski]
- Fix checkpatch line-length warnings (patches 3, 5, 6)
- Rate-limit link state send failure and ACK error warnings (patch 7)
[Jakub Kicinski]
- Correct enic_link_check comment to describe actual PF link state
notification flow (patch 7) [Simon Horman]
- Correct mbox_expected_reply comment — serialization is by
RTNL/probe, not mbox_lock (patch 8) [Jakub Kicinski]
- Wire enic_mbox_send_link_state() from enic_link_check() so PF
notifies VFs on carrier change (patch 9) [Simon Horman]
- Fix commit message wording about MSI-X reservation (patch 10)
[Simon Horman]
- Link to v7: https://patch.msgid.link/20260513-enic-sriov-v2-admin-channel-v2-v7-0-68b9f4141f4c@cisco.com
Changes in v7:
- Replace magic numbers in admin channel init with named macros
and inline comments for MBOX descriptor encoding
(patches 2, 6) [Paolo Abeni]
- Add defense-in-depth bounds check on admin RQ bytes_written (patch 4)
- Force NAPI reschedule on admin RQ refill failure (patch 4)
- Always unmask admin interrupt even with zero credits (patch 4)
- Reorder NAPI init before request_irq in admin channel open (patch 4)
- Remove redundant netdev_warn on admin msg enqueue kmalloc failure
(patch 4) [Paolo Abeni]
- Add netdev_warn on admin WQ/RQ disable failure in close path
(patch 2)
- Remove incorrect RES_TYPE_SRIOV_INTR interrupt allocation from
admin channel open (patch 2); interrupt setup handled entirely
in patch 4 using RES_TYPE_INTR_CTRL
- Rate-limit VF register/unregister log messages (patch 7) [Paolo Abeni]
- Add __aligned(8) to admin message data[] for strict-alignment
safety (patch 4)
- Rate-limit MBOX handler error warnings (patch 7)
- Pre-allocate port profile array before pci_disable_sriov in V1
disable path to avoid half-torn-down state on alloc failure (patch 9)
- Account for admin channel interrupt reservation in
enic_set_intr_mode() and enic_adjust_resources() (patch 9) [Paolo Abeni]
- Clear admin_rq_handler in enic_admin_channel_close (patch 9)
- Quiesce admin channel (mask interrupt, disable NAPI, block MBOX
sends) around soft reset (patch 9)
- Use WRITE_ONCE/READ_ONCE for mbox_send_disabled and
admin_rq_handler across data-path/reset boundaries
(patches 4, 6, 9)
- Fix commit message: reference enic_adjust_resources() alongside
enic_set_intr_mode() (patch 10)
Investigated findings from automated review (Simon Horman / Sashiko):
- Race between probe-time feature check and VF proxy: false positive;
detection runs at probe, enable runs from sriov_configure
- Struct alignment of __le32 after 2-byte mbox_hdr_embed: compiler
inserts correct padding, no manual alignment needed
- Stale MBOX reply matching / reinit_completion race: single-flight
design with mutex serialization prevents this
- cancel_work_sync vs MBOX unregister race: work cannot be
re-triggered during the close window
- Link to v6: https://patch.msgid.link/20260503-enic-sriov-v2-admin-channel-v2-v6-0-0af4fbc2d86d@cisco.com
Changes in v6:
- Add explanatory comments documenting admin_cq[0] (WQ CQE size) and
admin_cq[1] (RQ CQE size matching firmware enic_ext_cq() programming)
allocations (patch 2)
- Enforce bytes_written from CQ descriptor when enqueuing admin RQ
message; previously buf->len (allocation size) was passed, exposing
uninitialized buffer memory beyond the real payload (patch 4)
- Drop admin RQ messages with TRUNCATED set or FCS_OK clear, gated by
netdev_warn_once() (patch 4)
- Disable interrupt_enable on admin_cq[0]: WQ completions are polled
synchronously inside enic_mbox_send_msg() and never raise an
interrupt; matches admin_cq[1] (RQ) which does NAPI polling (patch 4)
- Add mbox_expected_reply gating in VF reply handlers (capability,
register, unregister): drop replies whose type does not match the
current waiter's expected type, avoiding spurious wakeup of an
unrelated waiter from a stale reply that arrives after timeout
(patch 8)
- Distinguish error returns in enic_mbox_vf_unregister(): -ETIMEDOUT
(no reply received), -EACCES (PF rejected the unregister), 0 on
success. Previously all paths collapsed to a single -ETIMEDOUT
(patch 8)
- Reserve one extra MSI-X slot in enic_set_intr_mode() when
has_admin_channel is set so enic_admin_setup_intr() always has room
to allocate at intr_count without exceeding intr_avail bounds when
data queue count is maxed out (patch 10)
- Clarify in commit messages that .sriov_configure is intentionally
not yet wired in this series and will be added in a follow-up after
the necessary devcmd hardening lands (patch 9)
- Link to v5: https://patch.msgid.link/20260423-enic-sriov-v2-admin-channel-v2-v5-0-caa9f504a3dc@cisco.com
Changes in v5:
- Fix DMA-into-freed-memory race: call enic_admin_qp_type_set() before
disabling RQ/WQ in both error and close paths (patch 3)
- Fix DMA mapping leak: enic_admin_wq_buf_clean() now unmaps and frees
WQ buffers still held at close time after a send timeout (patch 3)
- Log rate-limited warning on admin RQ refill failure (patch 4)
- Add missing linux/types.h and linux/bits.h includes to enic_mbox.h
(patch 5)
- Guard mbox_lock/mbox_comp init with mbox_initialized flag to prevent
re-initialization on sriov_configure re-entry (patch 7)
- Clear VF registered state before sending unregister reply so PF does
not treat a dead VF as still registered (patch 8)
- Gate VF-facing log messages with net_ratelimit() to prevent malicious
VF from flooding PF dmesg (patch 8)
- Reject VF port profile requests when V2 SR-IOV is active since
enic->pp is not reallocated for V2 VFs (patch 9)
- Move enic_sriov_detect_vf_type() before auto-enable check; skip
probe-time auto-enable for V2 VFs (patch 9)
- Move admin channel close and VF unregister before unregister_netdev()
in enic_remove() to prevent use-after-free on netdev (patch 10)
- Add comment in enic_reset() documenting that admin channel is not
recovered after soft reset (patch 10)
- Bypass RES_TYPE_SRIOV_INTR check for V2 VFs in admin channel
capability detection (patch 10)
- Link to v4: https://patch.msgid.link/20260411-enic-sriov-v2-admin-channel-v2-v4-0-f052326c2a57@cisco.com
Changes in v4:
- Fix reverse xmas tree variable ordering (patches 1, 6)
- Use kzalloc_obj instead of kzalloc with sizeof (patch 9)
- Add NULL check for pp allocation in V1 SR-IOV disable path (patch 9)
- Link to v3: https://lore.kernel.org/r/20260408-enic-sriov-v2-admin-channel-v2-v3-0-1d4999a03cec@cisco.com
Changes in v3:
- Use early-return pattern in enic_sriov_detect_vf_type to reduce
nesting (patch 1) [Breno Leitao]
- Link to v2: https://lore.kernel.org/r/20260408-enic-sriov-v2-admin-channel-v2-v2-0-d05dd3623fd3@cisco.com
Changes in v2:
- Fix lines exceeding 80 columns (patches 4, 6, 7, 8)
- Add __maybe_unused to enic_sriov_configure and enic_sriov_v2_enable;
.sriov_configure wiring deferred to a later series after devcmd
hardening is in place (patch 9)
- Guard probe-time auto-enable to skip V2 VFs (patch 9)
- Link to v1: https://lore.kernel.org/r/20260406-enic-sriov-v2-admin-channel-v2-v1-0-82cc47636a78@cisco.com
---
Satish Kharat (12):
enic: cancel tx_hang_reset work on device removal
enic: verify firmware supports V2 SR-IOV at probe time
enic: add admin channel open and close for SR-IOV
enic: add admin RQ buffer management
enic: add admin CQ service with MSI-X interrupt and workqueue polling
enic: define MBOX message types and header structures
enic: add MBOX core send and receive for admin channel
enic: add MBOX PF handlers for VF register and capability
enic: add MBOX VF handlers for capability, register and link state
enic: wire V2 SR-IOV enable with admin channel and MBOX
enic: add V2 VF probe with admin channel and PF registration
enic: re-establish V2 VF admin channel and PF registration after reset
drivers/net/ethernet/cisco/enic/Makefile | 3 +-
drivers/net/ethernet/cisco/enic/enic.h | 40 +-
drivers/net/ethernet/cisco/enic/enic_admin.c | 645 ++++++++++++++++++++++++++
drivers/net/ethernet/cisco/enic/enic_admin.h | 27 ++
drivers/net/ethernet/cisco/enic/enic_main.c | 394 +++++++++++++++-
drivers/net/ethernet/cisco/enic/enic_mbox.c | 640 +++++++++++++++++++++++++
drivers/net/ethernet/cisco/enic/enic_mbox.h | 95 ++++
drivers/net/ethernet/cisco/enic/enic_pp.c | 5 +
drivers/net/ethernet/cisco/enic/enic_res.c | 4 +-
drivers/net/ethernet/cisco/enic/vnic_cq.h | 9 +
drivers/net/ethernet/cisco/enic/vnic_devcmd.h | 13 +
drivers/net/ethernet/cisco/enic/vnic_enet.h | 4 +-
12 files changed, 1859 insertions(+), 20 deletions(-)
---
base-commit: ce6b4d3216b63f902bb8e9695ee6c10c83415f65
change-id: 20260404-enic-sriov-v2-admin-channel-v2-c0aa3e988833
Best regards,
--
Satish Kharat <satishkh@cisco.com>
^ permalink raw reply
* [PATCH net-next v12 02/12] enic: verify firmware supports V2 SR-IOV at probe time
From: Satish Kharat @ 2026-07-19 8:41 UTC (permalink / raw)
To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni
Cc: netdev, linux-kernel, Breno Leitao, Sesidhar Baddela,
Satish Kharat
In-Reply-To: <20260719-enic-sriov-v2-admin-channel-v2-v12-0-6ff2c617001d@cisco.com>
During PF probe, query the firmware get-supported-feature interface
to verify that the running firmware supports V2 SR-IOV. Firmware
version 5.3(4.72) and later report VIC_FEATURE_SRIOV via
CMD_GET_SUPP_FEATURE_VER. If the firmware does not support the
feature, set vf_type to ENIC_VF_TYPE_NONE and log a warning so the
admin knows a firmware upgrade is needed.
The V2 admin-channel and MBOX bring-up added later in this series is
gated on ENIC_VF_TYPE_V2, so this downgrade keeps those paths from
running on firmware that does not support V2 SR-IOV.
VIC_FEATURE_SRIOV is assigned the explicit value 4 to match the
firmware ABI. Slot 3 (firmware's VIC_FEATURE_PTP) is reserved with
a comment rather than a placeholder enum entry, since PTP is not
used by the upstream driver.
Suggested-by: Breno Leitao <leitao@debian.org>
Signed-off-by: Satish Kharat <satishkh@cisco.com>
Reviewed-by: Breno Leitao <leitao@debian.org>
---
drivers/net/ethernet/cisco/enic/enic_main.c | 21 ++++++++++++++++++++-
drivers/net/ethernet/cisco/enic/vnic_devcmd.h | 2 ++
2 files changed, 22 insertions(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/cisco/enic/enic_main.c b/drivers/net/ethernet/cisco/enic/enic_main.c
index b65796d96efc..6992411bd3b5 100644
--- a/drivers/net/ethernet/cisco/enic/enic_main.c
+++ b/drivers/net/ethernet/cisco/enic/enic_main.c
@@ -2641,8 +2641,10 @@ static void enic_iounmap(struct enic *enic)
static void enic_sriov_detect_vf_type(struct enic *enic)
{
struct pci_dev *pdev = enic->pdev;
- int pos;
+ u64 supported_versions, a1 = 0;
u16 vf_dev_id;
+ int pos;
+ int err;
if (enic_is_sriov_vf(enic) || enic_is_dynamic(enic))
return;
@@ -2669,6 +2671,23 @@ static void enic_sriov_detect_vf_type(struct enic *enic)
enic->vf_type = ENIC_VF_TYPE_NONE;
break;
}
+
+ if (enic->vf_type != ENIC_VF_TYPE_V2)
+ return;
+
+ /* A successful command means firmware recognizes
+ * VIC_FEATURE_SRIOV; supported_versions is available
+ * for sub-feature versioning in the future.
+ */
+ err = vnic_dev_get_supported_feature_ver(enic->vdev,
+ VIC_FEATURE_SRIOV,
+ &supported_versions,
+ &a1);
+ if (err) {
+ dev_warn(&pdev->dev,
+ "SR-IOV V2 not supported by current firmware. Upgrade to VIC FW 5.3(4.72) or higher.\n");
+ enic->vf_type = ENIC_VF_TYPE_NONE;
+ }
}
#endif
diff --git a/drivers/net/ethernet/cisco/enic/vnic_devcmd.h b/drivers/net/ethernet/cisco/enic/vnic_devcmd.h
index 605ef17f967e..3b6efa743dba 100644
--- a/drivers/net/ethernet/cisco/enic/vnic_devcmd.h
+++ b/drivers/net/ethernet/cisco/enic/vnic_devcmd.h
@@ -734,6 +734,8 @@ enum vic_feature_t {
VIC_FEATURE_VXLAN,
VIC_FEATURE_RDMA,
VIC_FEATURE_VXLAN_PATCH,
+ /* slot 3 reserved for firmware VIC_FEATURE_PTP */
+ VIC_FEATURE_SRIOV = 4,
VIC_FEATURE_MAX,
};
--
2.43.0
^ permalink raw reply related
* Re: [PATCH net] ipconfig: bound BOOTP options by UDP datagram length
From: Ido Schimmel @ 2026-07-19 8:41 UTC (permalink / raw)
To: Yizhou Zhao
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: <20260713141107.10418-1-zhaoyz24@mails.tsinghua.edu.cn>
On Mon, Jul 13, 2026 at 10:11:06PM +0800, Yizhou Zhao wrote:
> ic_bootp_recv() accepts an IP packet whose total length exceeds its UDP
> datagram length. This is valid at the IP layer, but DHCP/BOOTP options
> belong to the UDP payload. The option parser nevertheless uses the IP
> total length as its end boundary.
>
> Consequently, an on-link attacker that can race a boot-time DHCP exchange
> can put DHCP options after the declared UDP payload and have ipconfig
> consume them. A QEMU/KVM reproduction with ip=dhcp accepted a forged
> DHCPOFFER/DHCPACK with an IP total length of 290 and UDP length of 248;
> the tail supplied a netmask and an off-link gateway, causing boot-time
> network configuration to fail.
>
> Use the UDP datagram end as the parser boundary. The receiver already
> verifies that the UDP datagram lies within the IP packet, so this retains
> normal valid DHCP/BOOTP parsing while excluding the IP-only tail.
>
> Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
> Cc: stable@vger.kernel.org
> Reported-by: Yizhou Zhao <zhaoyz24@mails.tsinghua.edu.cn>
> Reported-by: Yuxiang Yang <yangyx22@mails.tsinghua.edu.cn>
> Reported-by: Ao Wang <wangao@seu.edu.cn>
> Reported-by: Xuewei Feng <fengxw06@126.com>
> Reported-by: Qi Li <qli01@tsinghua.edu.cn>
> Reported-by: Ke Xu <xuke@tsinghua.edu.cn>
> Assisted-by: Claude-Code:GLM-5.2-special
> Signed-off-by: Yizhou Zhao <zhaoyz24@mails.tsinghua.edu.cn>
The code change looks correct, but why frame it as a security fix?
Can't an "on-link attacker" forge the UDP payload instead?
^ permalink raw reply
* Re: Please backport bridge multicast exponential field encoding fix series to 6.1.y/6.6.y/6.12.y/6.18.y/7.0.y
From: Ido Schimmel @ 2026-07-19 8:22 UTC (permalink / raw)
To: Ujjal Roy
Cc: Andrew Lunn, Greg KH, Linux Stable, David S . Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Nikolay Aleksandrov, David Ahern, Shuah Khan, Andy Roulin,
Yong Wang, Petr Machata, Ujjal Roy, bridge, netdev, linux-kernel,
linux-kselftest
In-Reply-To: <CAE2MWkmbdDrCw1HUKm17QG7dyihuxg_1VGTUNeBq9v8NVwJV3Q@mail.gmail.com>
On Fri, Jul 17, 2026 at 04:12:09PM +0530, Ujjal Roy wrote:
> > It does not force the users to use a value < 128. You would need to
> > return EINVAL for that, which i'm not proposing. Returning an error
> > could break user space.
> >
>
> While I understand that clamping avoids breaking user space, silently
> modifying the value might lead to unexpected behavior that is
> difficult for users to debug.
> Because of these side effects, I am hesitant to proceed without the
> Multicast maintainer's input. We should raise an open issue on this
> topic to get their official decision, otherwise my preference is to
> ignore patching this for the stable kernel versions entirely.
We asked to target these patches at net-next because they don't fix a
regression (the encoding never worked correctly), so they are not
relevant for stable. Users that require this functionality should
upgrade to a new kernel that contains the patches.
Regarding the stable-specific patch, in my experience, stable
maintainers don't accept such patches and prefer to backport patches
from upstream, even if it results in more changes.
^ permalink raw reply
* Re: [PATCH net] rds: tcp: unregister sysctl before tearing down listen socket
From: Allison Henderson @ 2026-07-19 8:13 UTC (permalink / raw)
To: Cen Zhang (Microsoft)
Cc: davem, edumazet, kuba, pabeni, horms, netdev, linux-rdma,
rds-devel, linux-kernel, AutonomousCodeSecurity, tgopinath, kys
In-Reply-To: <20260718183420.13301-1-blbllhy@gmail.com>
On Sat, 2026-07-18 at 14:34 -0400, Cen Zhang (Microsoft) wrote:
> rds_tcp_exit_net() frees the per-netns RDS TCP listen socket via
> rds_tcp_kill_sock() before unregistering the per-netns sysctl table. Since
> rds_tcp_skbuf_handler() derives the netns from rtn->rds_tcp_listen_sock->sk,
> a concurrent sysctl write can race with netns teardown and dereference the
> freed socket/sk.
Hi Cen,
Thanks for working on this. The race is real and the analysis is right.
Some comments below:
>
> KASAN reports the race as:
>
> BUG: KASAN: slab-use-after-free in rds_tcp_skbuf_handler+0x2aa/0x2e0
> rds_tcp_skbuf_handler net/rds/tcp.c:721
> proc_sys_call_handler fs/proc/proc_sysctl.c
> vfs_write fs/read_write.c
> __x64_sys_pwrite64 fs/read_write.c
Was this stack actually observed or was it derived from an analysis? If it was
derived that's fine but just note it somewhere so that someone doesnt end up
chasing a synthesized stack trace. If you did actually hit it though, please
include the full report and a reproducer if you have it.
>
> Fix this by unregistering the RDS TCP sysctl table before calling
> rds_tcp_kill_sock(). unregister_net_sysctl_table() prevents new sysctl
> handlers from starting and waits for in-flight handlers to finish, so
> the listen socket can then be released safely.
>
> Fixes: 7f5611cbc487 ("rds: sysctl: rds_tcp_{rcv,snd}buf: avoid using current->nsproxy")
> Reported-by: AutonomousCodeSecurity@microsoft.com
Check patch generates a warning here for a Closes: or Link: tag.
If the reporting tool you're using generates a public report, please include the link here
> Signed-off-by: Cen Zhang (Microsoft) <blbllhy@gmail.com>
Other than that I think the fix is ok. With the above fixed, you can add my rvb:
Reviewed-by: Allison Henderson <achender@kernel.org>
Thanks!
Allison
> ---
> net/rds/tcp.c | 4 ++--
> 1 file changed, 2 insertions(+), 2 deletions(-)
>
> diff --git a/net/rds/tcp.c b/net/rds/tcp.c
> index a1de114d5e2e..453d4077a85e 100644
> --- a/net/rds/tcp.c
> +++ b/net/rds/tcp.c
> @@ -655,13 +655,13 @@ static void __net_exit rds_tcp_exit_net(struct net *net)
> {
> struct rds_tcp_net *rtn = net_generic(net, rds_tcp_netid);
>
> - rds_tcp_kill_sock(net);
> -
> if (rtn->rds_tcp_sysctl)
> unregister_net_sysctl_table(rtn->rds_tcp_sysctl);
>
> if (net != &init_net)
> kfree(rtn->ctl_table);
> +
> + rds_tcp_kill_sock(net);
> }
>
> static struct pernet_operations rds_tcp_net_ops = {
^ permalink raw reply
* Re: [PATCH net-next v6 1/2] net: dsa: realtek: rtl8365mb: add SGMII support for RTL8367S
From: Stanislaw @ 2026-07-19 7:35 UTC (permalink / raw)
To: Johan Alvarado, Mieczyslaw Nalewaj
Cc: Linus Walleij, Alvin Sipraga, Andrew Lunn, Vladimir Oltean,
David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Russell King, Maxime Chevallier, Luiz Angelo Daros de Luca,
netdev, linux-kernel
In-Reply-To: <CAJq09z4TX=LFOX7iq2g_JwkjtLM1U_Q=aYvAkd123LgOwZNyUw@mail.gmail.com>
Hi Luiz,
Two updates from the bench: last night's cold-soak result, and a
correction about the reset GPIO.
1) A 3-minute pre-init wait PREVENTS the bad state.
Last night's image carried a plain msleep(180000) at the top of
rtl83xx_probe(), before anything touches the chip - and nothing else
on top of the v6 series (no busy-wait, no 0x060C writes). After ~10 h
powered off, the first init landed clean for the first time: 0 FCS
errors on the SerDes CPU port, 0 drop events, DHCP lease right away,
0% loss on the trunk. Every comparable soak before (same hardware,
overnight off, no wait) came up degraded. Single morning so far - I
will repeat it - but this is the first thing that has *prevented* the
state rather than cured it after the fact.
During those 180 s the chip is powered and out of its (bus-level)
reset, just untouched by the driver. So your time-vs-state
discriminator leans "time": the very same init sequence that lands
bad when run at t=3.5 s lands clean when run at t=187 s. It does not
fully separate "the switch needs time" from "something else on the
board needs time", but nothing else shows any distress at t=3.5 s -
the SoC-side MAC/PCS come up fine, and even in the bad state the
switch's transmit direction is byte-exact clean.
2) Correction: the realtek driver performs NO hard reset here.
I mis-stated this earlier: reset-gpios on this board sits on the MDIO
*bus* node (the IPQ5018 MDIO bus driver toggles it once at bus init),
not on the switch node. rtl83xx_probe() therefore sees neither
reset_ctl nor a reset gpio and the hard-reset branch never runs - I
confirmed it with a print inside that branch. So the curing re-probe
is a pure *soft* full init (detect + complete setup/jam-table
sequence, the only chip reset being the soft one in setup), no pin
involved. The "double reset in probe" scenario does not apply on this
board at all.
3) Next steps, along your suggestions:
- Bisect the required off-time with the no-wait image, to get
reproductions on demand instead of one per night.
- On the next on-demand bad state: full register dump via regmap
debugfs, bad state vs. post-cure, plus the reset-bit isolation.
- Then bisect the wait itself (180 s -> 90 -> 45 ...) to find the
threshold - that number should hint at which physical process we
are waiting out.
Best regards,
Stanislaw
^ permalink raw reply
* Re: [PATCH net-next 0/2] ipv6: report why a route was deleted in RTM_DELROUTE
From: Ido Schimmel @ 2026-07-19 6:53 UTC (permalink / raw)
To: Yuyang Huang
Cc: David S. Miller, Bobby Eshleman, Chris J Arges, Daniel Zahka,
David Ahern, David Wei, Dimitri Daskalakis, Donald Hunter,
Eric Dumazet, Gal Pressman, Jakub Kicinski, Paolo Abeni,
Shuah Khan, Simon Horman, Stanislav Fomichev, Willem de Bruijn,
linux-kernel, linux-kselftest, netdev
In-Reply-To: <20260717222327.7398-1-sigefriedhyy@gmail.com>
On Sat, Jul 18, 2026 at 07:23:25AM +0900, Yuyang Huang wrote:
> When the kernel deletes an IPv6 route on its own, the RTM_DELROUTE
> notification does not say why. User space cannot tell a route that
> expired from one the router explicitly withdrew, yet the two call for
> different reactions: an expired RA route means the router failed to
> refresh it in time, which points at a misconfigured or unreliable
> router and may warrant action such as disabling IPv6 on that network,
> while a zero-lifetime withdrawal is normal, RFC-compliant operation.
Please expand more on the motivation: Which user space application is
going to consume this information and what is it going to do with it?
Also, Sashiko has some valid comments. Please take a look.
^ permalink raw reply
* Re: 答复: [外部邮件] Re: [PATCH] net: ipv6: fix a potential use-after-free in ip4ip6_err
From: Ido Schimmel @ 2026-07-19 6:37 UTC (permalink / raw)
To: Li,Rongqing
Cc: Xin Long, David Ahern, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, netdev@vger.kernel.org,
linux-kernel@vger.kernel.org
In-Reply-To: <098518d8f02746a0abeec70110dcb5b8@baidu.com>
On Sat, Jul 18, 2026 at 12:43:48AM +0000, Li,Rongqing wrote:
>
>
> > -----邮件原件-----
> > 发件人: Xin Long <lucien.xin@gmail.com>
> > 发送时间: 2026年7月17日 23:53
> > 收件人: Li,Rongqing <lirongqing@baidu.com>
> > 抄送: David Ahern <dsahern@kernel.org>; Ido Schimmel
> > <idosch@nvidia.com>; David S . Miller <davem@davemloft.net>; Eric
> > Dumazet <edumazet@google.com>; Jakub Kicinski <kuba@kernel.org>; Paolo
> > Abeni <pabeni@redhat.com>; Simon Horman <horms@kernel.org>;
> > netdev@vger.kernel.org; linux-kernel@vger.kernel.org
> > 主题: [外部邮件] Re: [PATCH] net: ipv6: fix a potential use-after-free in
> > ip4ip6_err
> >
> > On Fri, Jul 17, 2026 at 10:33 AM lirongqing <lirongqing@baidu.com> wrote:
> > >
> > > From: Li RongQing <lirongqing@baidu.com>
> > >
> > > Fix a use-after-free bug in ip4ip6_err() where rt->rt_flags is
> > > accessed after the route entry object has been released via ip_rt_put(rt).
> > >
> > > If ip_rt_put() decrements the reference count to zero and frees the
> > > rtable structure, reading rt->rt_flags immediately afterward results
> > > in a use-after-free pointer dereference.
> > >
> > > Fix this by caching rt->rt_flags into a local variable before calling
> > > ip_rt_put().
> > >
> > > Fixes: 77552cfa39c4 ("ip6_tunnel: clean up ip4ip6 and ip6ip6's
> > > err_handlers")
> > > Signed-off-by: Li RongQing <lirongqing@baidu.com>
> > > ---
> > > net/ipv6/ip6_tunnel.c | 4 +++-
> > > 1 file changed, 3 insertions(+), 1 deletion(-)
> > >
> > > diff --git a/net/ipv6/ip6_tunnel.c b/net/ipv6/ip6_tunnel.c index
> > > bf8e40a..984cb0c 100644
> > > --- a/net/ipv6/ip6_tunnel.c
> > > +++ b/net/ipv6/ip6_tunnel.c
> > > @@ -569,6 +569,7 @@ ip4ip6_err(struct sk_buff *skb, struct
> > > inet6_skb_parm *opt, {
> > > __u32 rel_info = ntohl(info);
> > > const struct iphdr *eiph;
> > > + unsigned int rt_flags;
> > > struct sk_buff *skb2;
> > > int err, rel_msg = 0;
> > > u8 rel_type = type;
> > > @@ -627,10 +628,11 @@ ip4ip6_err(struct sk_buff *skb, struct
> > inet6_skb_parm *opt,
> > > goto out;
> > >
> > > skb2->dev = rt->dst.dev;
> > > + rt_flags = rt->rt_flags;
> > > ip_rt_put(rt);
> > >
> > > /* route "incoming" packet */
> > > - if (rt->rt_flags & RTCF_LOCAL) {
> > > + if (rt_flags & RTCF_LOCAL) {
> > > rt = ip_route_output_ports(dev_net(skb->dev), &fl4,
> > NULL,
> > > eiph->daddr, eiph->saddr,
> > 0, 0,
> > > IPPROTO_IPIP,
> > > --
> > > 2.9.4
> > >
> > Have you already seen any problem triggered by this?
> >
> > I don't really think there's a use-after-free issue here.
> >
> > The entire IPv6 input path, including ICMPv6 error handling, runs under
> > rcu_read_lock(). Since dst_release() uses call_rcu_hurry() to defer the actual
> > freeing until after the RCU grace period, accessing rt->rt_flags after
> > ip_rt_put(rt) is completely safe in this context.
> >
>
> Thanks for the review. However, The issue is that after ip_rt_put(rt), the rt pointer may be logically freed if refcnt drops to zero, and dereferencing it afterwards is illegal even if memory isn't recycled immediately. The patch avoids this by caching rt->rt_flags before the put operation. This is a defensive fix against potential UAF reported by static analyzers.
I agree with Xin. I don't see how a UAF can happen here. If you are
claiming to fix a use-after-free, then reproduce it and put a relevant
trace in the commit message.
^ permalink raw reply
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