| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/08/12 13:48 | flow | patch-triage |
8mArgs:null Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"KMSANReasoning": "",
"NeedsKMSAN": false,
"Reasoning": "The patch only modifies the `enic` driver (Cisco VIC Ethernet NIC), which requires physical PCIe hardware not emulated in standard QEMU environments. Thus, the code is unreachable during fuzzing.",
"WorthFuzzing": false
} |
| 1/1 | 2026/08/12 13:48 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 46abf18d9f48b69fed08a54013c2eed2f6ed0670\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Wed Aug 12 13:48:44 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/drivers/net/ethernet/cisco/enic/Makefile b/drivers/net/ethernet/cisco/enic/Makefile\nindex a96b8332e6e2a..e38aaf34c1483 100644\n--- a/drivers/net/ethernet/cisco/enic/Makefile\n+++ b/drivers/net/ethernet/cisco/enic/Makefile\n@@ -3,5 +3,6 @@ obj-$(CONFIG_ENIC) := enic.o\n \n enic-y := enic_main.o vnic_cq.o vnic_intr.o vnic_wq.o \\\n \tenic_res.o enic_dev.o enic_pp.o vnic_dev.o vnic_rq.o vnic_vic.o \\\n-\tenic_ethtool.o enic_api.o enic_clsf.o enic_rq.o enic_wq.o\n+\tenic_ethtool.o enic_api.o enic_clsf.o enic_rq.o enic_wq.o \\\n+\tenic_admin.o enic_mbox.o\n \ndiff --git a/drivers/net/ethernet/cisco/enic/enic.h b/drivers/net/ethernet/cisco/enic/enic.h\nindex 08472420f3a1f..4a67947cfb9f3 100644\n--- a/drivers/net/ethernet/cisco/enic/enic.h\n+++ b/drivers/net/ethernet/cisco/enic/enic.h\n@@ -256,10 +256,10 @@ struct enic {\n \tstruct enic_rx_coal rx_coalesce_setting;\n \tu32 rx_coalesce_usecs;\n \tu32 tx_coalesce_usecs;\n-#ifdef CONFIG_PCI_IOV\n \tu16 num_vfs;\n-#endif\n \tenum enic_vf_type vf_type;\n+\tbool vf_registered;\n+\tu32 pf_cap_version;\n \tunsigned int enable_count;\n \tspinlock_t enic_api_lock;\n \tbool enic_api_busy;\n@@ -292,10 +292,46 @@ struct enic {\n \n \t/* Admin channel resources for SR-IOV MBOX */\n \tbool has_admin_channel;\n+\t/* true only while the admin WQ/RQ/CQ are allocated and enabled; gates\n+\t * enic_admin_channel_close() so it is a no-op after a failed (re)open\n+\t * left the resources freed.\n+\t */\n+\tbool admin_chan_up;\n+\t/* set on send timeout; cleared on channel re-open */\n+\tbool mbox_send_disabled;\n \tstruct vnic_wq admin_wq;\n \tstruct vnic_rq admin_rq;\n \tstruct vnic_cq admin_cq[2];\n \tstruct vnic_intr admin_intr;\n+\tstruct delayed_work admin_poll_work;\n+\tunsigned int admin_intr_index;\n+\tstruct work_struct link_notify_work;\n+\tstruct work_struct admin_msg_work;\n+\tspinlock_t admin_msg_lock;\t/* protects admin_msg_list */\n+\tstruct list_head admin_msg_list;\n+\tunsigned int admin_msg_count;\t/* current depth of admin_msg_list */\n+\tvoid (*admin_rq_handler)(struct enic *enic, void *buf,\n+\t\t\t\t unsigned int len);\n+\n+\t/* MBOX protocol state — mbox_lock serializes admin WQ sends */\n+\tstruct mutex mbox_lock;\n+\tu64 mbox_msg_num;\n+\t/* MBOX request-reply state. mbox_expected_reply is written and\n+\t * cleared by the process-context request helpers (capability/register/\n+\t * unregister) and only read by the admin_msg_work receive handlers, so\n+\t * it is annotated with READ_ONCE()/WRITE_ONCE() rather than locked:\n+\t * only one request is in flight at a time (requesters run under RTNL or\n+\t * single-threaded probe/remove), so each request is serialized and its\n+\t * reply completes mbox_comp before the next request is issued.\n+\t */\n+\tstruct completion mbox_comp;\n+\tu8 mbox_expected_reply;\n+\tbool mbox_initialized;\n+\n+\t/* PF: per-VF MBOX state, allocated when SRIOV V2 is enabled */\n+\tstruct enic_vf_state {\n+\t\tbool registered;\n+\t} *vf_state;\n };\n \n static inline struct net_device *vnic_get_netdev(struct vnic_dev *vdev)\n@@ -417,6 +453,7 @@ void enic_reset_addr_lists(struct enic *enic);\n int enic_sriov_enabled(struct enic *enic);\n int enic_is_valid_vf(struct enic *enic, int vf);\n int enic_is_dynamic(struct enic *enic);\n+int enic_is_sriov_vf_v2(struct enic *enic);\n void enic_set_ethtool_ops(struct net_device *netdev);\n int __enic_set_rsskey(struct enic *enic);\n void enic_ext_cq(struct enic *enic);\ndiff --git a/drivers/net/ethernet/cisco/enic/enic_admin.c b/drivers/net/ethernet/cisco/enic/enic_admin.c\nnew file mode 100644\nindex 0000000000000..7188f1b81c046\n--- /dev/null\n+++ b/drivers/net/ethernet/cisco/enic/enic_admin.c\n@@ -0,0 +1,667 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+// Copyright 2025 Cisco Systems, Inc. All rights reserved.\n+\n+#include \u003clinux/kernel.h\u003e\n+#include \u003clinux/netdevice.h\u003e\n+#include \u003clinux/dma-mapping.h\u003e\n+#include \u003clinux/interrupt.h\u003e\n+\n+#include \"vnic_dev.h\"\n+#include \"vnic_wq.h\"\n+#include \"vnic_rq.h\"\n+#include \"vnic_cq.h\"\n+#include \"vnic_intr.h\"\n+#include \"vnic_resource.h\"\n+#include \"vnic_devcmd.h\"\n+#include \"enic.h\"\n+#include \"enic_admin.h\"\n+#include \"cq_desc.h\"\n+#include \"cq_enet_desc.h\"\n+#include \"wq_enet_desc.h\"\n+#include \"rq_enet_desc.h\"\n+#include \"enic_mbox.h\"\n+\n+/* Retry interval for a failed admin RQ refill. Short so the control\n+ * channel recovers quickly once memory is available again.\n+ */\n+#define ENIC_ADMIN_RQ_REFILL_RETRY_MS\t100\n+\n+/* Clean up any admin WQ buffers still held by hardware at close time.\n+ * Normally buffers are freed inline after send completion, but a timed-out\n+ * send intentionally leaves the buffer live until the queue is stopped.\n+ */\n+static void enic_admin_wq_buf_clean(struct vnic_wq *wq,\n+\t\t\t\t struct vnic_wq_buf *buf)\n+{\n+\tstruct enic *enic = vnic_dev_priv(wq-\u003evdev);\n+\n+\tif (buf-\u003eos_buf) {\n+\t\tdma_unmap_single(\u0026enic-\u003epdev-\u003edev, buf-\u003edma_addr,\n+\t\t\t\t buf-\u003elen, DMA_TO_DEVICE);\n+\t\tkfree(buf-\u003eos_buf);\n+\t\tbuf-\u003eos_buf = NULL;\n+\t}\n+}\n+\n+static void enic_admin_rq_buf_clean(struct vnic_rq *rq,\n+\t\t\t\t struct vnic_rq_buf *buf)\n+{\n+\tstruct enic *enic = vnic_dev_priv(rq-\u003evdev);\n+\n+\tif (!buf-\u003eos_buf)\n+\t\treturn;\n+\n+\tdma_unmap_single(\u0026enic-\u003epdev-\u003edev, buf-\u003edma_addr, buf-\u003elen,\n+\t\t\t DMA_FROM_DEVICE);\n+\tkfree(buf-\u003eos_buf);\n+\tbuf-\u003eos_buf = NULL;\n+}\n+\n+static int enic_admin_rq_post_one(struct enic *enic, gfp_t gfp)\n+{\n+\tstruct vnic_rq *rq = \u0026enic-\u003eadmin_rq;\n+\tstruct rq_enet_desc *desc;\n+\tdma_addr_t dma_addr;\n+\tvoid *buf;\n+\n+\tbuf = kzalloc(ENIC_ADMIN_BUF_SIZE, gfp);\n+\tif (!buf)\n+\t\treturn -ENOMEM;\n+\n+\tdma_addr = dma_map_single(\u0026enic-\u003epdev-\u003edev, buf, ENIC_ADMIN_BUF_SIZE,\n+\t\t\t\t DMA_FROM_DEVICE);\n+\tif (dma_mapping_error(\u0026enic-\u003epdev-\u003edev, dma_addr)) {\n+\t\tkfree(buf);\n+\t\treturn -ENOMEM;\n+\t}\n+\n+\tdesc = vnic_rq_next_desc(rq);\n+\trq_enet_desc_enc(desc, (u64)dma_addr | VNIC_PADDR_TARGET,\n+\t\t\t RQ_ENET_TYPE_ONLY_SOP, ENIC_ADMIN_BUF_SIZE);\n+\tvnic_rq_post(rq, buf, 0, dma_addr, ENIC_ADMIN_BUF_SIZE, 0);\n+\n+\treturn 0;\n+}\n+\n+static int enic_admin_rq_fill(struct enic *enic, gfp_t gfp)\n+{\n+\tstruct vnic_rq *rq = \u0026enic-\u003eadmin_rq;\n+\tint err;\n+\n+\twhile (vnic_rq_desc_avail(rq) \u003e 0) {\n+\t\terr = enic_admin_rq_post_one(enic, gfp);\n+\t\tif (err)\n+\t\t\treturn err;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static void enic_admin_rq_drain(struct enic *enic)\n+{\n+\tvnic_rq_clean(\u0026enic-\u003eadmin_rq, enic_admin_rq_buf_clean);\n+}\n+\n+static unsigned int enic_admin_cq_color(void *cq_desc, unsigned int desc_size)\n+{\n+\tu8 type_color = *((u8 *)cq_desc + desc_size - 1);\n+\n+\treturn (type_color \u003e\u003e CQ_DESC_COLOR_SHIFT) \u0026 CQ_DESC_COLOR_MASK;\n+}\n+\n+unsigned int enic_admin_wq_cq_service(struct enic *enic)\n+{\n+\tstruct vnic_cq *cq = \u0026enic-\u003eadmin_cq[0];\n+\tunsigned int work = 0;\n+\tvoid *desc;\n+\n+\tdesc = vnic_cq_to_clean(cq);\n+\twhile (enic_admin_cq_color(desc, cq-\u003ering.desc_size) !=\n+\t cq-\u003elast_color) {\n+\t\tvnic_cq_inc_to_clean(cq);\n+\t\twork++;\n+\t\tdesc = vnic_cq_to_clean(cq);\n+\t}\n+\n+\treturn work;\n+}\n+\n+/* Upper bound on pending admin messages. A buggy or hostile VF could flood\n+ * the PF admin channel faster than admin_msg_work drains it; cap the backlog\n+ * so a guest cannot drive the host out of memory.\n+ */\n+#define ENIC_ADMIN_MSG_MAX\t256\n+\n+static void enic_admin_msg_enqueue(struct enic *enic, void *buf,\n+\t\t\t\t unsigned int len)\n+{\n+\tstruct enic_admin_msg *msg;\n+\n+\tmsg = kmalloc(struct_size(msg, data, len), GFP_KERNEL);\n+\tif (!msg)\n+\t\treturn;\n+\n+\tmsg-\u003elen = len;\n+\tmemcpy(msg-\u003edata, buf, len);\n+\n+\tspin_lock(\u0026enic-\u003eadmin_msg_lock);\n+\tif (enic-\u003eadmin_msg_count \u003e= ENIC_ADMIN_MSG_MAX) {\n+\t\tspin_unlock(\u0026enic-\u003eadmin_msg_lock);\n+\t\tkfree(msg);\n+\t\tif (net_ratelimit())\n+\t\t\tnetdev_warn(enic-\u003enetdev,\n+\t\t\t\t \"admin msg backlog full (%u); dropping\\n\",\n+\t\t\t\t ENIC_ADMIN_MSG_MAX);\n+\t\treturn;\n+\t}\n+\tlist_add_tail(\u0026msg-\u003elist, \u0026enic-\u003eadmin_msg_list);\n+\tenic-\u003eadmin_msg_count++;\n+\tspin_unlock(\u0026enic-\u003eadmin_msg_lock);\n+}\n+\n+unsigned int enic_admin_rq_cq_service(struct enic *enic)\n+{\n+\tstruct vnic_cq *cq = \u0026enic-\u003eadmin_cq[1];\n+\tstruct vnic_rq *rq = \u0026enic-\u003eadmin_rq;\n+\tstruct cq_enet_rq_desc *rq_desc;\n+\tstruct vnic_rq_buf *buf;\n+\tu16 bwf, bytes_written;\n+\tunsigned int work = 0;\n+\tvoid *desc;\n+\n+\t/* The admin RQ and its CQ form a single in-order channel: firmware\n+\t * posts exactly one CQE per consumed RQ descriptor, in submission\n+\t * order. Each CQE therefore pairs with rq-\u003eto_clean below without a\n+\t * completed_index cross-check, mirroring the in-order assumption of\n+\t * the main enic RX path.\n+\t */\n+\tdesc = vnic_cq_to_clean(cq);\n+\twhile (enic_admin_cq_color(desc, cq-\u003ering.desc_size) !=\n+\t cq-\u003elast_color) {\n+\t\t/* Ensure DMA descriptor fields are read after\n+\t\t * the color/valid check. dma_rmb() is the\n+\t\t * correct barrier for DMA-written descriptors.\n+\t\t */\n+\t\tdma_rmb();\n+\t\tbuf = rq-\u003eto_clean;\n+\n+\t\t/* Decode the actual number of bytes hardware wrote into\n+\t\t * the RX buffer. buf-\u003elen is the static allocation size\n+\t\t * (ENIC_ADMIN_BUF_SIZE); copying that many bytes would read\n+\t\t * beyond the actual DMA payload. bytes_written_flags is at\n+\t\t * the same offset in every cq_enet_rq_desc[_32|_64] variant.\n+\t\t */\n+\t\trq_desc = desc;\n+\t\tbwf = le16_to_cpu(rq_desc-\u003ebytes_written_flags);\n+\t\tbytes_written = bwf \u0026 CQ_ENET_RQ_DESC_BYTES_WRITTEN_MASK;\n+\t\tif (bytes_written \u003e buf-\u003elen)\n+\t\t\tgoto next_desc;\n+\n+\t\tdma_sync_single_for_cpu(\u0026enic-\u003epdev-\u003edev,\n+\t\t\t\t\tbuf-\u003edma_addr, buf-\u003elen,\n+\t\t\t\t\tDMA_FROM_DEVICE);\n+\n+\t\t/* Drop on hardware error indications. Admin messages\n+\t\t * are internal to the VIC, not received over the wire.\n+\t\t * Firmware sets TRUNCATED when the message does not fit\n+\t\t * in the posted buffer, and FCS_OK is always set on\n+\t\t * healthy admin completions.\n+\t\t */\n+\t\tif (bwf \u0026 CQ_ENET_RQ_DESC_FLAGS_TRUNCATED) {\n+\t\t\tnetdev_warn_once(enic-\u003enetdev,\n+\t\t\t\t\t \"admin RQ: truncated message dropped\\n\");\n+\t\t\tgoto next_desc;\n+\t\t}\n+\t\tif (!(rq_desc-\u003eflags \u0026 CQ_ENET_RQ_DESC_FLAGS_FCS_OK)) {\n+\t\t\tnetdev_warn_once(enic-\u003enetdev,\n+\t\t\t\t\t \"admin RQ: bad FCS, dropping message\\n\");\n+\t\t\tgoto next_desc;\n+\t\t}\n+\n+\t\tif (enic-\u003eadmin_rq_handler) {\n+\t\t\tu16 sender_vlan;\n+\n+\t\t\t/* Firmware sets the CQ VLAN field to identify the\n+\t\t\t * sender: 0 = PF, 1-based = VF index. Overwrite\n+\t\t\t * the untrusted src_vnic_id in the MBOX header with\n+\t\t\t * the hardware-verified value.\n+\t\t\t */\n+\t\t\tsender_vlan = le16_to_cpu(rq_desc-\u003evlan);\n+\t\t\tif (bytes_written \u003e= sizeof(struct enic_mbox_hdr)) {\n+\t\t\t\tstruct enic_mbox_hdr *hdr = buf-\u003eos_buf;\n+\n+\t\t\t\thdr-\u003esrc_vnic_id = (sender_vlan == 0) ?\n+\t\t\t\t\tcpu_to_le16(ENIC_MBOX_DST_PF) :\n+\t\t\t\t\tcpu_to_le16(sender_vlan - 1);\n+\t\t\t}\n+\n+\t\t\tenic_admin_msg_enqueue(enic, buf-\u003eos_buf,\n+\t\t\t\t\t bytes_written);\n+\t\t}\n+\n+next_desc:\n+\t\tenic_admin_rq_buf_clean(rq, rq-\u003eto_clean);\n+\t\trq-\u003eto_clean = rq-\u003eto_clean-\u003enext;\n+\t\trq-\u003ering.desc_avail++;\n+\n+\t\tvnic_cq_inc_to_clean(cq);\n+\t\twork++;\n+\t\tdesc = vnic_cq_to_clean(cq);\n+\t}\n+\n+\tif (enic_admin_rq_fill(enic, GFP_KERNEL)) {\n+\t\t/* Some RX buffers could not be reposted (transient memory\n+\t\t * pressure). If the ring is left empty the channel would stall\n+\t\t * with no completion to drive the next refill, so arm a delayed\n+\t\t * re-run of this poll work (the sole owner of the admin RQ ring)\n+\t\t * to repost buffers and re-arm the RQ from a single context.\n+\t\t */\n+\t\tif (net_ratelimit())\n+\t\t\tnetdev_warn(enic-\u003enetdev,\n+\t\t\t\t \"admin RQ refill failed; scheduling retry\\n\");\n+\t\tschedule_delayed_work(\u0026enic-\u003eadmin_poll_work,\n+\t\t\t\t msecs_to_jiffies(ENIC_ADMIN_RQ_REFILL_RETRY_MS));\n+\t}\n+\n+\treturn work;\n+}\n+\n+static irqreturn_t enic_admin_isr_msix(int irq, void *data)\n+{\n+\tstruct enic *enic = data;\n+\n+\tschedule_delayed_work(\u0026enic-\u003eadmin_poll_work, 0);\n+\n+\treturn IRQ_HANDLED;\n+}\n+\n+static void enic_admin_msg_work_handler(struct work_struct *work)\n+{\n+\tstruct enic *enic = container_of(work, struct enic, admin_msg_work);\n+\tstruct enic_admin_msg *msg, *tmp;\n+\tLIST_HEAD(local_list);\n+\n+\tspin_lock_bh(\u0026enic-\u003eadmin_msg_lock);\n+\tlist_splice_init(\u0026enic-\u003eadmin_msg_list, \u0026local_list);\n+\tenic-\u003eadmin_msg_count = 0;\n+\tspin_unlock_bh(\u0026enic-\u003eadmin_msg_lock);\n+\n+\tlist_for_each_entry_safe(msg, tmp, \u0026local_list, list) {\n+\t\tif (enic-\u003eadmin_rq_handler)\n+\t\t\tenic-\u003eadmin_rq_handler(enic, msg-\u003edata, msg-\u003elen);\n+\t\tlist_del(\u0026msg-\u003elist);\n+\t\tkfree(msg);\n+\t}\n+}\n+\n+static void enic_admin_poll_work_handler(struct work_struct *work)\n+{\n+\tstruct enic *enic = container_of(to_delayed_work(work), struct enic,\n+\t\t\t\t\t admin_poll_work);\n+\tunsigned int credits;\n+\tunsigned int rq_work;\n+\n+\t/* Snapshot the pending credit count before draining so we acknowledge\n+\t * exactly what the hardware reported for this interrupt. Credits that\n+\t * accrue while enic_admin_rq_cq_service() runs are left for the next\n+\t * interrupt, which is harmless on this low-rate control path.\n+\t */\n+\tcredits = vnic_intr_credits(\u0026enic-\u003eadmin_intr);\n+\n+\trq_work = enic_admin_rq_cq_service(enic);\n+\n+\tif (rq_work \u003e 0)\n+\t\tschedule_work(\u0026enic-\u003eadmin_msg_work);\n+\n+\t/* Acknowledge the snapshotted credits and unmask the vector. Unlike\n+\t * the NAPI data path, the admin channel is not re-polled, so the vector\n+\t * must be re-armed here to receive the next completion. The unmask is\n+\t * applied through the interrupt mask register independently of the\n+\t * credit count, so returning zero credits on a spurious wakeup still\n+\t * re-arms the vector.\n+\t */\n+\tvnic_intr_return_credits(\u0026enic-\u003eadmin_intr,\n+\t\t\t\t credits,\n+\t\t\t\t 1 /* unmask */, 0);\n+}\n+\n+static int enic_admin_setup_intr(struct enic *enic)\n+{\n+\tunsigned int intr_index = enic-\u003eintr_count;\n+\tint err;\n+\n+\tif (vnic_dev_get_intr_mode(enic-\u003evdev) != VNIC_DEV_INTR_MODE_MSIX ||\n+\t intr_index \u003e= enic-\u003eintr_avail)\n+\t\treturn -ENODEV;\n+\n+\t/* The admin INTR uses a slot in the same RES_TYPE_INTR_CTRL\n+\t * strided array of per-vector control blocks (mask, coalescing\n+\t * timer, credit return) that the data-path IRQs occupy in BAR0.\n+\t * vnic_intr_alloc() defaults to RES_TYPE_INTR_CTRL, which is what\n+\t * we want here.\n+\t */\n+\terr = vnic_intr_alloc(enic-\u003evdev, \u0026enic-\u003eadmin_intr, intr_index);\n+\tif (err) {\n+\t\tnetdev_warn(enic-\u003enetdev,\n+\t\t\t \"Failed to alloc admin intr at index %u: %d\\n\",\n+\t\t\t intr_index, err);\n+\t\treturn err;\n+\t}\n+\n+\tenic-\u003eadmin_intr_index = intr_index;\n+\n+\t/* Mask the admin vector before requesting the IRQ so an early or\n+\t * spurious completion cannot run the poll handler against the\n+\t * not-yet-initialised admin rings. enic_admin_channel_open() unmasks\n+\t * it only after the rings are initialised and filled.\n+\t */\n+\tvnic_intr_mask(\u0026enic-\u003eadmin_intr);\n+\n+\t/* A V2 VF opens the admin channel during probe, before\n+\t * register_netdev() resolves the \"eth%d\" name template, so using\n+\t * netdev-\u003ename here would register the literal \"eth%d-admin\" in\n+\t * /proc/interrupts. Use the already-stable PCI device name instead.\n+\t */\n+\tsnprintf(enic-\u003emsix[intr_index].devname,\n+\t\t sizeof(enic-\u003emsix[intr_index].devname),\n+\t\t \"%s-admin\", pci_name(enic-\u003epdev));\n+\tenic-\u003emsix[intr_index].isr = enic_admin_isr_msix;\n+\tenic-\u003emsix[intr_index].devid = enic;\n+\n+\terr = request_irq(enic-\u003emsix_entry[intr_index].vector,\n+\t\t\t enic-\u003emsix[intr_index].isr, 0,\n+\t\t\t enic-\u003emsix[intr_index].devname,\n+\t\t\t enic-\u003emsix[intr_index].devid);\n+\tif (err) {\n+\t\tnetdev_warn(enic-\u003enetdev,\n+\t\t\t \"Failed to request admin MSI-X irq: %d\\n\", err);\n+\t\tvnic_intr_free(\u0026enic-\u003eadmin_intr);\n+\t\treturn err;\n+\t}\n+\n+\tenic-\u003emsix[intr_index].requested = 1;\n+\n+\tnetdev_dbg(enic-\u003enetdev,\n+\t\t \"admin channel using MSI-X interrupt (index %u)\\n\",\n+\t\t intr_index);\n+\n+\treturn 0;\n+}\n+\n+static void enic_admin_teardown_intr(struct enic *enic)\n+{\n+\tunsigned int intr_index = enic-\u003eadmin_intr_index;\n+\n+\tfree_irq(enic-\u003emsix_entry[intr_index].vector,\n+\t\t enic-\u003emsix[intr_index].devid);\n+\tcancel_delayed_work_sync(\u0026enic-\u003eadmin_poll_work);\n+\tenic-\u003emsix[intr_index].requested = 0;\n+}\n+\n+static int enic_admin_qp_type_set(struct enic *enic, u32 enable)\n+{\n+\tu64 a0 = QP_TYPE_ADMIN, a1 = enable;\n+\tint wait = 1000;\n+\tint err;\n+\n+\tspin_lock_bh(\u0026enic-\u003edevcmd_lock);\n+\terr = vnic_dev_cmd(enic-\u003evdev, CMD_QP_TYPE_SET, \u0026a0, \u0026a1, wait);\n+\tspin_unlock_bh(\u0026enic-\u003edevcmd_lock);\n+\n+\treturn err;\n+}\n+\n+static int enic_admin_alloc_resources(struct enic *enic)\n+{\n+\tint err;\n+\n+\terr = vnic_wq_alloc_with_type(enic-\u003evdev, \u0026enic-\u003eadmin_wq, 0,\n+\t\t\t\t ENIC_ADMIN_DESC_COUNT,\n+\t\t\t\t sizeof(struct wq_enet_desc),\n+\t\t\t\t RES_TYPE_ADMIN_WQ);\n+\tif (err)\n+\t\treturn err;\n+\n+\terr = vnic_rq_alloc_with_type(enic-\u003evdev, \u0026enic-\u003eadmin_rq, 0,\n+\t\t\t\t ENIC_ADMIN_DESC_COUNT,\n+\t\t\t\t sizeof(struct rq_enet_desc),\n+\t\t\t\t RES_TYPE_ADMIN_RQ);\n+\tif (err)\n+\t\tgoto free_wq;\n+\n+\t/* admin_cq[0] is the WQ completion queue. WQ CQEs are always\n+\t * 16 bytes wide; firmware always writes 16-byte CQEs for WQ\n+\t * completions on every WQ, including the admin channel WQ.\n+\t * Use sizeof(struct cq_desc) accordingly.\n+\t */\n+\terr = vnic_cq_alloc_with_type(enic-\u003evdev, \u0026enic-\u003eadmin_cq[0], 0,\n+\t\t\t\t ENIC_ADMIN_DESC_COUNT,\n+\t\t\t\t sizeof(struct cq_desc),\n+\t\t\t\t RES_TYPE_ADMIN_CQ);\n+\tif (err)\n+\t\tgoto free_rq;\n+\n+\t/* admin_cq[1] is the RQ completion queue. Its descriptor size\n+\t * must match what firmware writes. enic_ext_cq() called earlier\n+\t * in probe issues CMD_CQ_ENTRY_SIZE_SET for VNIC_RQ_ALL,\n+\t * programming firmware to write CQ entries of (16 \u003c\u003c enic-\u003eext_cq)\n+\t * bytes for every RQ CQ on the vNIC, including the admin RQ CQ.\n+\t * Allocating with the same size keeps the host poller and\n+\t * firmware in lockstep:\n+\t *\n+\t * - The color/valid bit lives at byte (desc_size - 1) of every\n+\t * cq_enet_rq_desc[_32|_64] variant, so enic_admin_cq_color()\n+\t * reads it from the correct offset.\n+\t * - Only the first 15 bytes of the descriptor (vlan,\n+\t * bytes_written_flags, ...) are accessed by the admin path;\n+\t * these fields are identical across all three variants (see\n+\t * comment in enic_rq.c above cq_enet_rq_desc_dec()).\n+\t */\n+\terr = vnic_cq_alloc_with_type(enic-\u003evdev, \u0026enic-\u003eadmin_cq[1], 1,\n+\t\t\t\t ENIC_ADMIN_DESC_COUNT,\n+\t\t\t\t 16 \u003c\u003c enic-\u003eext_cq,\n+\t\t\t\t RES_TYPE_ADMIN_CQ);\n+\tif (err)\n+\t\tgoto free_cq0;\n+\n+\treturn 0;\n+\n+free_cq0:\n+\tvnic_cq_free(\u0026enic-\u003eadmin_cq[0]);\n+free_rq:\n+\tvnic_rq_free(\u0026enic-\u003eadmin_rq);\n+free_wq:\n+\tvnic_wq_free(\u0026enic-\u003eadmin_wq);\n+\treturn err;\n+}\n+\n+static void enic_admin_free_resources(struct enic *enic)\n+{\n+\tvnic_intr_free(\u0026enic-\u003eadmin_intr);\n+\tvnic_cq_free(\u0026enic-\u003eadmin_cq[1]);\n+\tvnic_cq_free(\u0026enic-\u003eadmin_cq[0]);\n+\tvnic_rq_free(\u0026enic-\u003eadmin_rq);\n+\tvnic_wq_free(\u0026enic-\u003eadmin_wq);\n+}\n+\n+static void enic_admin_init_resources(struct enic *enic)\n+{\n+\tunsigned int intr_offset = enic-\u003eadmin_intr_index;\n+\n+\tvnic_wq_init(\u0026enic-\u003eadmin_wq,\n+\t\t 0, 0, 0); /* cq_index, err_intr_enable, err_intr_offset */\n+\tvnic_rq_init(\u0026enic-\u003eadmin_rq,\n+\t\t 1, 0, 0); /* cq_index, err_intr_enable, err_intr_offset */\n+\tvnic_cq_init(\u0026enic-\u003eadmin_cq[0],\n+\t\t VNIC_CQ_FC_DISABLE,\n+\t\t VNIC_CQ_COLOR_ENABLE,\n+\t\t 0, 0, 1, /* cq_head, cq_tail, cq_tail_color */\n+\t\t VNIC_CQ_INTR_DISABLE, /* polled synchronously by mbox send */\n+\t\t VNIC_CQ_ENTRY_ENABLE,\n+\t\t VNIC_CQ_MSG_DISABLE,\n+\t\t intr_offset,\n+\t\t 0 /* cq_message_addr */);\n+\tvnic_cq_init(\u0026enic-\u003eadmin_cq[1],\n+\t\t VNIC_CQ_FC_DISABLE,\n+\t\t VNIC_CQ_COLOR_ENABLE,\n+\t\t 0, 0, 1, /* cq_head, cq_tail, cq_tail_color */\n+\t\t VNIC_CQ_INTR_ENABLE,\n+\t\t VNIC_CQ_ENTRY_ENABLE,\n+\t\t VNIC_CQ_MSG_DISABLE,\n+\t\t intr_offset,\n+\t\t 0 /* cq_message_addr */);\n+\t/* coalescing_timer, coalescing_type, mask_on_assertion */\n+\tvnic_intr_init(\u0026enic-\u003eadmin_intr,\n+\t\t 0, 0, 1);\n+}\n+\n+static void enic_admin_msg_drain(struct enic *enic)\n+{\n+\tstruct enic_admin_msg *msg, *tmp;\n+\n+\tspin_lock_bh(\u0026enic-\u003eadmin_msg_lock);\n+\tlist_for_each_entry_safe(msg, tmp, \u0026enic-\u003eadmin_msg_list, list) {\n+\t\tlist_del(\u0026msg-\u003elist);\n+\t\tkfree(msg);\n+\t}\n+\tenic-\u003eadmin_msg_count = 0;\n+\tspin_unlock_bh(\u0026enic-\u003eadmin_msg_lock);\n+}\n+\n+int enic_admin_channel_open(struct enic *enic)\n+{\n+\tint err;\n+\n+\tif (!enic-\u003ehas_admin_channel)\n+\t\treturn -ENODEV;\n+\n+\t/* Keep MBOX sends disabled for the entire open sequence. It is\n+\t * cleared only after every resource is allocated and enabled below,\n+\t * so any early error return here leaves sends disabled and a\n+\t * concurrent sender cannot touch a half-open or freed admin_wq.\n+\t */\n+\tWRITE_ONCE(enic-\u003embox_send_disabled, true);\n+\n+\terr = enic_admin_alloc_resources(enic);\n+\tif (err) {\n+\t\tnetdev_err(enic-\u003enetdev,\n+\t\t\t \"Failed to alloc admin channel resources: %d\\n\",\n+\t\t\t err);\n+\t\treturn err;\n+\t}\n+\n+\tspin_lock_init(\u0026enic-\u003eadmin_msg_lock);\n+\tINIT_LIST_HEAD(\u0026enic-\u003eadmin_msg_list);\n+\tINIT_WORK(\u0026enic-\u003eadmin_msg_work, enic_admin_msg_work_handler);\n+\tINIT_DELAYED_WORK(\u0026enic-\u003eadmin_poll_work, enic_admin_poll_work_handler);\n+\n+\terr = enic_admin_setup_intr(enic);\n+\tif (err) {\n+\t\tnetdev_err(enic-\u003enetdev,\n+\t\t\t \"Admin channel requires MSI-X, SR-IOV unavailable: %d\\n\",\n+\t\t\t err);\n+\t\tgoto free_resources;\n+\t}\n+\n+\tenic_admin_init_resources(enic);\n+\n+\tvnic_wq_enable(\u0026enic-\u003eadmin_wq);\n+\tvnic_rq_enable(\u0026enic-\u003eadmin_rq);\n+\n+\terr = enic_admin_rq_fill(enic, GFP_KERNEL);\n+\tif (err) {\n+\t\tnetdev_err(enic-\u003enetdev,\n+\t\t\t \"Failed to fill admin RQ buffers: %d\\n\", err);\n+\t\tgoto disable_queues;\n+\t}\n+\n+\terr = enic_admin_qp_type_set(enic, QP_ENABLE);\n+\tif (err) {\n+\t\tnetdev_err(enic-\u003enetdev,\n+\t\t\t \"Failed to set admin QP type: %d\\n\", err);\n+\t\tgoto disable_queues;\n+\t}\n+\n+\tvnic_intr_unmask(\u0026enic-\u003eadmin_intr);\n+\n+\t/* Only now that the admin WQ/RQ/CQ and interrupt are fully allocated,\n+\t * programmed and enabled is it safe to allow MBOX sends. Clearing this\n+\t * earlier opened a window where a concurrent sender (e.g. link-notify\n+\t * work scheduled by a post-reset link-up) could call enic_mbox_send_msg()\n+\t * against a not-yet-allocated admin_wq and crash.\n+\t */\n+\tWRITE_ONCE(enic-\u003embox_send_disabled, false);\n+\n+\tnetdev_dbg(enic-\u003enetdev,\n+\t\t \"admin channel open: intr=%u wq_avail=%u rq_avail=%u cq0_color=%u cq1_color=%u\\n\",\n+\t\t enic-\u003eadmin_intr_index,\n+\t\t vnic_wq_desc_avail(\u0026enic-\u003eadmin_wq),\n+\t\t vnic_rq_desc_avail(\u0026enic-\u003eadmin_rq),\n+\t\t enic-\u003eadmin_cq[0].last_color,\n+\t\t enic-\u003eadmin_cq[1].last_color);\n+\n+\tenic-\u003eadmin_chan_up = true;\n+\n+\treturn 0;\n+\n+disable_queues:\n+\tenic_admin_teardown_intr(enic);\n+\tenic_admin_qp_type_set(enic, QP_DISABLE);\n+\tif (vnic_wq_disable(\u0026enic-\u003eadmin_wq))\n+\t\tnetdev_warn(enic-\u003enetdev, \"Failed to disable admin WQ\\n\");\n+\tif (vnic_rq_disable(\u0026enic-\u003eadmin_rq))\n+\t\tnetdev_warn(enic-\u003enetdev, \"Failed to disable admin RQ\\n\");\n+\tcancel_work_sync(\u0026enic-\u003eadmin_msg_work);\n+\tenic_admin_msg_drain(enic);\n+\tenic_admin_rq_drain(enic);\n+free_resources:\n+\tenic_admin_free_resources(enic);\n+\treturn err;\n+}\n+\n+void enic_admin_channel_close(struct enic *enic)\n+{\n+\tint err;\n+\n+\tif (!enic-\u003ehas_admin_channel)\n+\t\treturn;\n+\n+\t/* Nothing to tear down if the channel was never (re)opened, e.g. a\n+\t * failed enic_admin_channel_open() in probe or in the reset path;\n+\t * otherwise the disable/clean calls below dereference freed resources.\n+\t */\n+\tif (!enic-\u003eadmin_chan_up)\n+\t\treturn;\n+\n+\tWRITE_ONCE(enic-\u003embox_send_disabled, true);\n+\n+\tnetdev_dbg(enic-\u003enetdev, \"admin channel close\\n\");\n+\n+\tvnic_intr_mask(\u0026enic-\u003eadmin_intr);\n+\tenic_admin_teardown_intr(enic);\n+\tcancel_work_sync(\u0026enic-\u003elink_notify_work);\n+\tcancel_work_sync(\u0026enic-\u003eadmin_msg_work);\n+\tenic_admin_msg_drain(enic);\n+\n+\tenic_admin_qp_type_set(enic, QP_DISABLE);\n+\n+\terr = vnic_wq_disable(\u0026enic-\u003eadmin_wq);\n+\tif (err)\n+\t\tnetdev_warn(enic-\u003enetdev,\n+\t\t\t \"Failed to disable admin WQ: %d\\n\", err);\n+\terr = vnic_rq_disable(\u0026enic-\u003eadmin_rq);\n+\tif (err)\n+\t\tnetdev_warn(enic-\u003enetdev,\n+\t\t\t \"Failed to disable admin RQ: %d\\n\", err);\n+\n+\tvnic_wq_clean(\u0026enic-\u003eadmin_wq, enic_admin_wq_buf_clean);\n+\tenic_admin_rq_drain(enic);\n+\tvnic_cq_clean(\u0026enic-\u003eadmin_cq[0]);\n+\tvnic_cq_clean(\u0026enic-\u003eadmin_cq[1]);\n+\tvnic_intr_clean(\u0026enic-\u003eadmin_intr);\n+\n+\tenic-\u003eadmin_rq_handler = NULL;\n+\tenic_admin_free_resources(enic);\n+\n+\tenic-\u003eadmin_chan_up = false;\n+}\ndiff --git a/drivers/net/ethernet/cisco/enic/enic_admin.h b/drivers/net/ethernet/cisco/enic/enic_admin.h\nnew file mode 100644\nindex 0000000000000..62c80220b0cac\n--- /dev/null\n+++ b/drivers/net/ethernet/cisco/enic/enic_admin.h\n@@ -0,0 +1,27 @@\n+/* SPDX-License-Identifier: GPL-2.0-only */\n+/* Copyright 2025 Cisco Systems, Inc. All rights reserved. */\n+\n+#ifndef _ENIC_ADMIN_H_\n+#define _ENIC_ADMIN_H_\n+\n+#define ENIC_ADMIN_DESC_COUNT\t64\n+#define ENIC_ADMIN_BUF_SIZE\t2048\n+\n+struct enic;\n+\n+/* Wrapper for received admin messages queued for deferred processing.\n+ * The admin CQ poll work handler enqueues these; a separate work handler\n+ * processes them where sleeping (mutex, GFP_KERNEL) is safe.\n+ */\n+struct enic_admin_msg {\n+\tstruct list_head list;\n+\tunsigned int len;\n+\tu8 data[] __aligned(8);\n+};\n+\n+int enic_admin_channel_open(struct enic *enic);\n+void enic_admin_channel_close(struct enic *enic);\n+unsigned int enic_admin_wq_cq_service(struct enic *enic);\n+unsigned int enic_admin_rq_cq_service(struct enic *enic);\n+\n+#endif /* _ENIC_ADMIN_H_ */\ndiff --git a/drivers/net/ethernet/cisco/enic/enic_main.c b/drivers/net/ethernet/cisco/enic/enic_main.c\nindex d98f7e7ccab9b..0baef7a120ec6 100644\n--- a/drivers/net/ethernet/cisco/enic/enic_main.c\n+++ b/drivers/net/ethernet/cisco/enic/enic_main.c\n@@ -60,6 +60,8 @@\n #include \"enic_clsf.h\"\n #include \"enic_rq.h\"\n #include \"enic_wq.h\"\n+#include \"enic_admin.h\"\n+#include \"enic_mbox.h\"\n \n #define ENIC_NOTIFY_TIMER_PERIOD\t(2 * HZ)\n \n@@ -314,6 +316,11 @@ static int enic_is_sriov_vf(struct enic *enic)\n \t enic-\u003epdev-\u003edevice == PCI_DEVICE_ID_CISCO_VIC_ENET_VF_V2;\n }\n \n+int enic_is_sriov_vf_v2(struct enic *enic)\n+{\n+\treturn enic-\u003epdev-\u003edevice == PCI_DEVICE_ID_CISCO_VIC_ENET_VF_V2;\n+}\n+\n int enic_is_valid_vf(struct enic *enic, int vf)\n {\n #ifdef CONFIG_PCI_IOV\n@@ -411,18 +418,50 @@ static void enic_set_rx_coal_setting(struct enic *enic)\n \trx_coal-\u003euse_adaptive_rx_coalesce = 1;\n }\n \n+static void enic_link_notify_work_handler(struct work_struct *work)\n+{\n+\tstruct enic *enic = container_of(work, struct enic,\n+\t\t\t\t\t link_notify_work);\n+\tu32 state;\n+\tu16 i;\n+\n+\tif (!enic_sriov_enabled(enic) || !enic-\u003evf_state)\n+\t\treturn;\n+\n+\tstate = netif_carrier_ok(enic-\u003enetdev) ?\n+\t\tENIC_MBOX_LINK_STATE_ENABLE :\n+\t\tENIC_MBOX_LINK_STATE_DISABLE;\n+\n+\tfor (i = 0; i \u003c enic-\u003enum_vfs; i++)\n+\t\tenic_mbox_send_link_state(enic, i, state);\n+}\n+\n static void enic_link_check(struct enic *enic)\n {\n-\tint link_status = vnic_dev_link_status(enic-\u003evdev);\n-\tint carrier_ok = netif_carrier_ok(enic-\u003enetdev);\n+\tint link_status;\n+\tint carrier_ok;\n+\n+\t/* A V2 SR-IOV VF's carrier is driven by PF link-state MBOX\n+\t * notifications, not by its own vnic link status; skip the\n+\t * autonomous check so it cannot flap the VF carrier.\n+\t */\n+\tif (enic_is_sriov_vf_v2(enic))\n+\t\treturn;\n+\n+\tlink_status = vnic_dev_link_status(enic-\u003evdev);\n+\tcarrier_ok = netif_carrier_ok(enic-\u003enetdev);\n \n \tif (link_status \u0026\u0026 !carrier_ok) {\n \t\tnetdev_info(enic-\u003enetdev, \"Link UP\\n\");\n \t\tnetif_carrier_on(enic-\u003enetdev);\n \t\tenic_set_rx_coal_setting(enic);\n+\t\tif (enic_sriov_enabled(enic) \u0026\u0026 enic-\u003evf_state)\n+\t\t\tschedule_work(\u0026enic-\u003elink_notify_work);\n \t} else if (!link_status \u0026\u0026 carrier_ok) {\n \t\tnetdev_info(enic-\u003enetdev, \"Link DOWN\\n\");\n \t\tnetif_carrier_off(enic-\u003enetdev);\n+\t\tif (enic_sriov_enabled(enic) \u0026\u0026 enic-\u003evf_state)\n+\t\t\tschedule_work(\u0026enic-\u003elink_notify_work);\n \t}\n }\n \n@@ -2142,6 +2181,74 @@ static void enic_set_api_busy(struct enic *enic, bool busy)\n \tspin_unlock(\u0026enic-\u003eenic_api_lock);\n }\n \n+/* The admin/MBOX channel exists on a V2 PF while SR-IOV is enabled and on\n+ * every V2 VF. A reset wipes the admin WQ/RQ/CQ, so such devices must tear\n+ * the channel down before the reset and re-establish it afterwards.\n+ */\n+static bool enic_has_admin_chan(struct enic *enic)\n+{\n+\treturn enic_is_sriov_vf_v2(enic) ||\n+\t (enic_sriov_enabled(enic) \u0026\u0026 enic-\u003evf_type == ENIC_VF_TYPE_V2);\n+}\n+\n+/* Re-establish the admin/MBOX channel after a reset has re-created the data\n+ * path. Mirrors the relevant part of the probe / SR-IOV-enable sequence:\n+ * reinitialise MBOX and reopen the channel, then for a VF re-run the PF\n+ * handshake (the reset wiped the VF's admin QP, so the VF must register\n+ * again), or for a PF re-push the current link state to registered VFs.\n+ */\n+static void enic_admin_chan_reopen(struct enic *enic)\n+{\n+\tint err;\n+\n+\t/* Install the MBOX receive handler and reset the sequence number\n+\t * before opening the channel, so the handler is in place before the\n+\t * admin interrupt is unmasked and no early completion is dropped.\n+\t */\n+\tenic_mbox_init(enic);\n+\n+\t/* A reset destroys the VF's local admin QP, so the VF can no longer\n+\t * rely on its previous registration. The PF may retain stale software\n+\t * registration state until the VF successfully registers again.\n+\t * Clear the local flag before reopening so a failed reopen or\n+\t * re-handshake cannot leave the VF believing it has a usable PF\n+\t * registration over a dead channel.\n+\t */\n+\tif (enic_is_sriov_vf_v2(enic))\n+\t\tenic-\u003evf_registered = false;\n+\n+\terr = enic_admin_channel_open(enic);\n+\tif (err) {\n+\t\tnetdev_err(enic-\u003enetdev,\n+\t\t\t \"admin channel reopen after reset failed: %d\\n\", err);\n+\t\treturn;\n+\t}\n+\n+\tif (enic_is_sriov_vf_v2(enic)) {\n+\t\terr = enic_mbox_vf_capability_check(enic);\n+\t\tif (err) {\n+\t\t\tnetdev_err(enic-\u003enetdev,\n+\t\t\t\t \"MBOX capability check after reset failed: %d\\n\",\n+\t\t\t\t err);\n+\t\t\tenic_admin_channel_close(enic);\n+\t\t\treturn;\n+\t\t}\n+\t\terr = enic_mbox_vf_register(enic);\n+\t\tif (err) {\n+\t\t\tnetdev_err(enic-\u003enetdev,\n+\t\t\t\t \"MBOX VF re-registration after reset failed: %d\\n\",\n+\t\t\t\t err);\n+\t\t\tenic_admin_channel_close(enic);\n+\t\t}\n+\t} else {\n+\t\t/* The link came back up during enic_open() above while MBOX\n+\t\t * sends were still disabled (channel not yet reopened), so that\n+\t\t * link-notify was dropped. Re-push current link state now.\n+\t\t */\n+\t\tschedule_work(\u0026enic-\u003elink_notify_work);\n+\t}\n+}\n+\n static void enic_reset(struct work_struct *work)\n {\n \tstruct enic *enic = container_of(work, struct enic, reset);\n@@ -2154,15 +2261,34 @@ static void enic_reset(struct work_struct *work)\n \t/* Stop any activity from infiniband */\n \tenic_set_api_busy(enic, true);\n \n+\t/* Fully tear down the V2 admin/MBOX channel before the soft reset.\n+\t * The reset wipes all hardware queues including the admin WQ/RQ;\n+\t * closing first tells firmware to stop the admin QP (so it no longer\n+\t * DMAs from the about-to-be-reset rings) and frees the admin resources\n+\t * so they are cleanly re-allocated afterwards.\n+\t */\n+\tif (enic_has_admin_chan(enic))\n+\t\tenic_admin_channel_close(enic);\n+\n \tenic_stop(enic-\u003enetdev);\n+\n \tenic_dev_soft_reset(enic);\n \tenic_reset_addr_lists(enic);\n \tenic_init_vnic_resources(enic);\n \tenic_set_rss_nic_cfg(enic);\n \tenic_dev_set_ig_vlan_rewrite_mode(enic);\n \tenic_ext_cq(enic);\n+\n \tenic_open(enic-\u003enetdev);\n \n+\t/* Re-establish the admin/MBOX channel after the data path is back up.\n+\t * It was fully torn down by enic_admin_channel_close() above;\n+\t * enic_admin_chan_reopen() reopens it and, for a PF re-pushes link\n+\t * state, or for a VF re-runs the probe-time PF handshake.\n+\t */\n+\tif (enic_has_admin_chan(enic))\n+\t\tenic_admin_chan_reopen(enic);\n+\n \t/* Allow infiniband to fiddle with the device again */\n \tenic_set_api_busy(enic, false);\n \n@@ -2180,16 +2306,33 @@ static void enic_tx_hang_reset(struct work_struct *work)\n \t/* Stop any activity from infiniband */\n \tenic_set_api_busy(enic, true);\n \n+\t/* Fully tear down the V2 admin/MBOX channel before the hang reset, for\n+\t * the same reason as the soft reset path: stop the admin QP and free\n+\t * the admin resources before the hardware queues are wiped.\n+\t */\n+\tif (enic_has_admin_chan(enic))\n+\t\tenic_admin_channel_close(enic);\n+\n \tenic_dev_hang_notify(enic);\n \tenic_stop(enic-\u003enetdev);\n+\n \tenic_dev_hang_reset(enic);\n \tenic_reset_addr_lists(enic);\n \tenic_init_vnic_resources(enic);\n \tenic_set_rss_nic_cfg(enic);\n \tenic_dev_set_ig_vlan_rewrite_mode(enic);\n \tenic_ext_cq(enic);\n+\n \tenic_open(enic-\u003enetdev);\n \n+\t/* Re-establish the admin/MBOX channel after the data path is back up.\n+\t * It was fully torn down by enic_admin_channel_close() above;\n+\t * enic_admin_chan_reopen() reopens it and, for a PF re-pushes link\n+\t * state, or for a VF re-runs the probe-time PF handshake.\n+\t */\n+\tif (enic_has_admin_chan(enic))\n+\t\tenic_admin_chan_reopen(enic);\n+\n \t/* Allow infiniband to fiddle with the device again */\n \tenic_set_api_busy(enic, false);\n \n@@ -2200,6 +2343,8 @@ static void enic_tx_hang_reset(struct work_struct *work)\n \n static int enic_set_intr_mode(struct enic *enic)\n {\n+\tunsigned int admin_reserve = enic-\u003ehas_admin_channel ? 1 : 0;\n+\tunsigned int min_intr = ENIC_MSIX_MIN_INTR + admin_reserve;\n \tunsigned int i;\n \tint num_intr;\n \n@@ -2210,12 +2355,12 @@ static int enic_set_intr_mode(struct enic *enic)\n \t */\n \n \tif (enic-\u003econfig.intr_mode \u003c 1 \u0026\u0026\n-\t enic-\u003eintr_avail \u003e= ENIC_MSIX_MIN_INTR) {\n+\t enic-\u003eintr_avail \u003e= min_intr) {\n \t\tfor (i = 0; i \u003c enic-\u003eintr_avail; i++)\n \t\t\tenic-\u003emsix_entry[i].entry = i;\n \n \t\tnum_intr = pci_enable_msix_range(enic-\u003epdev, enic-\u003emsix_entry,\n-\t\t\t\t\t\t ENIC_MSIX_MIN_INTR,\n+\t\t\t\t\t\t min_intr,\n \t\t\t\t\t\t enic-\u003eintr_avail);\n \t\tif (num_intr \u003e 0) {\n \t\t\tvnic_dev_set_intr_mode(enic-\u003evdev,\n@@ -2310,16 +2455,27 @@ static int enic_adjust_resources(struct enic *enic)\n \t\tenic-\u003ecq_count = 2;\n \t\tenic-\u003eintr_count = enic-\u003eintr_avail;\n \t\tbreak;\n-\tcase VNIC_DEV_INTR_MODE_MSIX:\n+\tcase VNIC_DEV_INTR_MODE_MSIX: {\n \t\t/* Adjust the number of wqs/rqs/cqs/interrupts that will be\n-\t\t * used based on which resource is the most constrained\n+\t\t * used based on which resource is the most constrained.\n+\t\t * Reserve one extra MSI-X slot for the admin channel INTR\n+\t\t * when has_admin_channel is set so that\n+\t\t * enic_admin_setup_intr() can allocate at intr_count\n+\t\t * within the intr_avail bounds even when the data queue\n+\t\t * count is maxed out. intr_count counts only the data-path\n+\t\t * IRQs (registered by enic_request_intr()); the admin INTR\n+\t\t * lives at msix index intr_count and is set up later by\n+\t\t * enic_admin_setup_intr().\n \t\t */\n+\t\tunsigned int admin_reserve = enic-\u003ehas_admin_channel ? 1 : 0;\n+\n \t\twq_avail = min(enic-\u003ewq_avail, ENIC_WQ_MAX);\n \t\trq_default = max(netif_get_num_default_rss_queues(),\n \t\t\t\t ENIC_RQ_MIN_DEFAULT);\n \t\trq_avail = min3(enic-\u003erq_avail, ENIC_RQ_MAX, rq_default);\n \t\tmax_queues = min(enic-\u003ecq_avail,\n-\t\t\t\t enic-\u003eintr_avail - ENIC_MSIX_RESERVED_INTR);\n+\t\t\t\t enic-\u003eintr_avail - ENIC_MSIX_RESERVED_INTR -\n+\t\t\t\t admin_reserve);\n \t\tif (wq_avail + rq_avail \u003c= max_queues) {\n \t\t\tenic-\u003erq_count = rq_avail;\n \t\t\tenic-\u003ewq_count = wq_avail;\n@@ -2337,6 +2493,7 @@ static int enic_adjust_resources(struct enic *enic)\n \t\tenic-\u003eintr_count = enic-\u003ecq_count + ENIC_MSIX_RESERVED_INTR;\n \n \t\tbreak;\n+\t}\n \tdefault:\n \t\tdev_err(enic_get_dev(enic), \"Unknown interrupt mode\\n\");\n \t\treturn -EINVAL;\n@@ -2641,8 +2798,10 @@ static void enic_iounmap(struct enic *enic)\n static void enic_sriov_detect_vf_type(struct enic *enic)\n {\n \tstruct pci_dev *pdev = enic-\u003epdev;\n-\tint pos;\n+\tu64 supported_versions, a1 = 0;\n \tu16 vf_dev_id;\n+\tint pos;\n+\tint err;\n \n \tif (enic_is_sriov_vf(enic) || enic_is_dynamic(enic))\n \t\treturn;\n@@ -2669,6 +2828,161 @@ static void enic_sriov_detect_vf_type(struct enic *enic)\n \t\tenic-\u003evf_type = ENIC_VF_TYPE_NONE;\n \t\tbreak;\n \t}\n+\n+\tif (enic-\u003evf_type != ENIC_VF_TYPE_V2)\n+\t\treturn;\n+\n+\t/* A successful command means firmware recognizes\n+\t * VIC_FEATURE_SRIOV; supported_versions is available\n+\t * for sub-feature versioning in the future.\n+\t */\n+\terr = vnic_dev_get_supported_feature_ver(enic-\u003evdev,\n+\t\t\t\t\t\t VIC_FEATURE_SRIOV,\n+\t\t\t\t\t\t \u0026supported_versions,\n+\t\t\t\t\t\t \u0026a1);\n+\tif (err) {\n+\t\tdev_warn(\u0026pdev-\u003edev,\n+\t\t\t \"SR-IOV V2 not supported by current firmware. Upgrade to VIC FW 5.3(4.72) or higher.\\n\");\n+\t\tenic-\u003evf_type = ENIC_VF_TYPE_NONE;\n+\t}\n+}\n+\n+static int __maybe_unused\n+enic_sriov_v2_enable(struct enic *enic, int num_vfs)\n+{\n+\tint err;\n+\n+\tif (!enic-\u003ehas_admin_channel) {\n+\t\tnetdev_err(enic-\u003enetdev,\n+\t\t\t \"V2 SR-IOV requires admin channel resources\\n\");\n+\t\treturn -EOPNOTSUPP;\n+\t}\n+\n+\tenic-\u003evf_state = kcalloc(num_vfs, sizeof(*enic-\u003evf_state), GFP_KERNEL);\n+\tif (!enic-\u003evf_state)\n+\t\treturn -ENOMEM;\n+\n+\t/* Install the MBOX receive handler before the admin interrupt is\n+\t * unmasked in enic_admin_channel_open(), so no early completion is\n+\t * dropped.\n+\t */\n+\tenic_mbox_init(enic);\n+\n+\terr = enic_admin_channel_open(enic);\n+\tif (err) {\n+\t\tnetdev_err(enic-\u003enetdev,\n+\t\t\t \"Failed to open admin channel: %d\\n\", err);\n+\t\tgoto free_vf_state;\n+\t}\n+\n+\tenic-\u003enum_vfs = num_vfs;\n+\n+\terr = pci_enable_sriov(enic-\u003epdev, num_vfs);\n+\tif (err) {\n+\t\tnetdev_err(enic-\u003enetdev,\n+\t\t\t \"pci_enable_sriov failed: %d\\n\", err);\n+\t\tgoto close_admin;\n+\t}\n+\n+\tenic-\u003epriv_flags |= ENIC_SRIOV_ENABLED;\n+\treturn num_vfs;\n+\n+close_admin:\n+\tenic-\u003enum_vfs = 0;\n+\tenic_admin_channel_close(enic);\n+free_vf_state:\n+\tkfree(enic-\u003evf_state);\n+\tenic-\u003evf_state = NULL;\n+\treturn err;\n+}\n+\n+static void enic_sriov_v2_disable(struct enic *enic)\n+{\n+\t/* Stop new VF link-state broadcasts before tearing down vf_state.\n+\t * Clearing ENIC_SRIOV_ENABLED makes enic_link_check() (called from\n+\t * the notify timer/ISR) skip the VF notify path, and cancelling\n+\t * link_notify_work ensures any already-queued broadcast has finished\n+\t * before vf_state is freed, closing a use-after-free window.\n+\t */\n+\tenic-\u003epriv_flags \u0026= ~ENIC_SRIOV_ENABLED;\n+\tcancel_work_sync(\u0026enic-\u003elink_notify_work);\n+\n+\tpci_disable_sriov(enic-\u003epdev);\n+\tenic_admin_channel_close(enic);\n+\tkfree(enic-\u003evf_state);\n+\tenic-\u003evf_state = NULL;\n+\tenic-\u003enum_vfs = 0;\n+}\n+\n+/*\n+ * enic_sriov_configure() and its V2 helpers are defined but not yet wired\n+ * into enic_driver via .sriov_configure (see the __maybe_unused annotations);\n+ * V2 enable/disable is activated in a follow-up series. Because the callback\n+ * is not registered, it cannot run concurrently with the rtnl-protected reset\n+ * paths (enic_reset(), enic_tx_hang_reset()) yet. Serialization against those\n+ * paths is added together with the .sriov_configure wiring in that series.\n+ */\n+static int __maybe_unused\n+enic_sriov_configure(struct pci_dev *pdev, int num_vfs)\n+{\n+\tstruct net_device *netdev = pci_get_drvdata(pdev);\n+\tstruct enic *enic = netdev_priv(netdev);\n+\tstruct enic_port_profile *pp;\n+\tint err;\n+\n+\tif (num_vfs \u003e 0) {\n+\t\tif (enic-\u003econfig.mq_subvnic_count) {\n+\t\t\tnetdev_err(netdev,\n+\t\t\t\t \"SR-IOV not supported with multi-queue sub-vnics\\n\");\n+\t\t\treturn -EOPNOTSUPP;\n+\t\t}\n+\n+\t\tif (enic-\u003evf_type == ENIC_VF_TYPE_NONE) {\n+\t\t\tnetdev_err(netdev,\n+\t\t\t\t \"SR-IOV not supported on this firmware version\\n\");\n+\t\t\treturn -EOPNOTSUPP;\n+\t\t}\n+\n+\t\tif (enic-\u003evf_type == ENIC_VF_TYPE_V2)\n+\t\t\treturn enic_sriov_v2_enable(enic, num_vfs);\n+\n+\t\tpp = kcalloc(num_vfs, sizeof(*pp), GFP_KERNEL);\n+\t\tif (!pp)\n+\t\t\treturn -ENOMEM;\n+\n+\t\terr = pci_enable_sriov(pdev, num_vfs);\n+\t\tif (err) {\n+\t\t\tkfree(pp);\n+\t\t\treturn err;\n+\t\t}\n+\n+\t\tkfree(enic-\u003epp);\n+\t\tenic-\u003epp = pp;\n+\t\tenic-\u003enum_vfs = num_vfs;\n+\t\tenic-\u003epriv_flags |= ENIC_SRIOV_ENABLED;\n+\t\treturn num_vfs;\n+\t}\n+\n+\tif (!enic_sriov_enabled(enic))\n+\t\treturn 0;\n+\n+\tif (enic-\u003evf_type == ENIC_VF_TYPE_V2) {\n+\t\tenic_sriov_v2_disable(enic);\n+\t\treturn 0;\n+\t}\n+\n+\tpp = kzalloc_obj(*enic-\u003epp, GFP_KERNEL);\n+\tif (!pp)\n+\t\treturn -ENOMEM;\n+\n+\tpci_disable_sriov(pdev);\n+\tenic-\u003enum_vfs = 0;\n+\tenic-\u003epriv_flags \u0026= ~ENIC_SRIOV_ENABLED;\n+\n+\tkfree(enic-\u003epp);\n+\tenic-\u003epp = pp;\n+\n+\treturn 0;\n }\n #endif\n \n@@ -2768,12 +3082,23 @@ static int enic_probe(struct pci_dev *pdev, const struct pci_device_id *ent)\n \t\tgoto err_out_vnic_unregister;\n \n #ifdef CONFIG_PCI_IOV\n-\t/* Get number of subvnics */\n+\tenic_sriov_detect_vf_type(enic);\n+\n+\t/* Auto-enable SR-IOV only for the legacy VF types. V2 VFs require\n+\t * the admin channel, which is not yet set up at probe time (V2 SR-IOV\n+\t * will be enabled through the sysfs .sriov_configure callback once a\n+\t * follow-up series wires it up); and a V2-capable device whose\n+\t * firmware lacks V2 support is downgraded to ENIC_VF_TYPE_NONE by\n+\t * enic_sriov_detect_vf_type() and must not be brought up through the\n+\t * legacy pci_enable_sriov() path either.\n+\t */\n \tpos = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_SRIOV);\n \tif (pos) {\n \t\tpci_read_config_word(pdev, pos + PCI_SRIOV_TOTAL_VF,\n \t\t\t\u0026enic-\u003enum_vfs);\n-\t\tif (enic-\u003enum_vfs) {\n+\t\tif (enic-\u003enum_vfs \u0026\u0026\n+\t\t (enic-\u003evf_type == ENIC_VF_TYPE_V1 ||\n+\t\t enic-\u003evf_type == ENIC_VF_TYPE_USNIC)) {\n \t\t\terr = pci_enable_sriov(pdev, enic-\u003enum_vfs);\n \t\t\tif (err) {\n \t\t\t\tdev_err(dev, \"SRIOV enable failed, aborting.\"\n@@ -2785,7 +3110,6 @@ static int enic_probe(struct pci_dev *pdev, const struct pci_device_id *ent)\n \t\t\tnum_pps = enic-\u003enum_vfs;\n \t\t}\n \t}\n-\tenic_sriov_detect_vf_type(enic);\n #endif\n \n \t/* Allocate structure for port profiles */\n@@ -2850,6 +3174,42 @@ static int enic_probe(struct pci_dev *pdev, const struct pci_device_id *ent)\n \t\tgoto err_out_dev_close;\n \t}\n \n+\t/* Initialise link_notify_work before the V2-VF admin-open block below:\n+\t * its error path (err_out_admin_close -\u003e enic_admin_channel_close() -\u003e\n+\t * cancel_work_sync()) would otherwise act on an uninitialised work.\n+\t */\n+\tINIT_WORK(\u0026enic-\u003elink_notify_work, enic_link_notify_work_handler);\n+\n+\t/* V2 VF: open admin channel and register with PF.\n+\t * Must happen before register_netdev so the VF is fully\n+\t * initialized before the interface is visible to userspace.\n+\t *\n+\t * enic_mbox_init() installs the receive handler and resets the\n+\t * sequence number; it must run before enic_admin_channel_open()\n+\t * unmasks the admin interrupt so an early completion is not dropped.\n+\t */\n+\tif (enic_is_sriov_vf_v2(enic)) {\n+\t\tenic_mbox_init(enic);\n+\t\terr = enic_admin_channel_open(enic);\n+\t\tif (err) {\n+\t\t\tdev_err(dev,\n+\t\t\t\t\"Failed to open admin channel: %d\\n\", err);\n+\t\t\tgoto err_out_dev_deinit;\n+\t\t}\n+\t\terr = enic_mbox_vf_capability_check(enic);\n+\t\tif (err) {\n+\t\t\tdev_err(dev,\n+\t\t\t\t\"MBOX capability check failed: %d\\n\", err);\n+\t\t\tgoto err_out_admin_close;\n+\t\t}\n+\t\terr = enic_mbox_vf_register(enic);\n+\t\tif (err) {\n+\t\t\tdev_err(dev,\n+\t\t\t\t\"MBOX VF registration failed: %d\\n\", err);\n+\t\t\tgoto err_out_admin_close;\n+\t\t}\n+\t}\n+\n \tnetif_set_real_num_tx_queues(netdev, enic-\u003ewq_count);\n \tnetif_set_real_num_rx_queues(netdev, enic-\u003erq_count);\n \n@@ -2874,7 +3234,7 @@ static int enic_probe(struct pci_dev *pdev, const struct pci_device_id *ent)\n \terr = enic_set_mac_addr(netdev, enic-\u003emac_addr);\n \tif (err) {\n \t\tdev_err(dev, \"Invalid MAC address, aborting\\n\");\n-\t\tgoto err_out_dev_deinit;\n+\t\tgoto err_out_admin_close;\n \t}\n \n \tenic-\u003etx_coalesce_usecs = enic-\u003econfig.intr_timer_usec;\n@@ -2972,11 +3332,23 @@ static int enic_probe(struct pci_dev *pdev, const struct pci_device_id *ent)\n \terr = register_netdev(netdev);\n \tif (err) {\n \t\tdev_err(dev, \"Cannot register net device, aborting\\n\");\n-\t\tgoto err_out_dev_deinit;\n+\t\tgoto err_out_admin_close;\n \t}\n \n \treturn 0;\n \n+err_out_admin_close:\n+\tif (enic_is_sriov_vf_v2(enic)) {\n+\t\tif (enic-\u003evf_registered) {\n+\t\t\tint unreg_err = enic_mbox_vf_unregister(enic);\n+\n+\t\t\tif (unreg_err)\n+\t\t\t\tnetdev_warn(netdev,\n+\t\t\t\t\t \"Failed to unregister from PF: %d\\n\",\n+\t\t\t\t\t unreg_err);\n+\t\t}\n+\t\tenic_admin_channel_close(enic);\n+\t}\n err_out_dev_deinit:\n \tenic_dev_deinit(enic);\n err_out_dev_close:\n@@ -3014,15 +3386,40 @@ static void enic_remove(struct pci_dev *pdev)\n \t\tdisable_work_sync(\u0026enic-\u003ereset);\n \t\tdisable_work_sync(\u0026enic-\u003etx_hang_reset);\n \t\tdisable_work_sync(\u0026enic-\u003echange_mtu_work);\n+\n+\t\t/* Close the admin channel and unregister from the PF before\n+\t\t * unregister_netdev() to prevent a late PF notification from\n+\t\t * touching a netdev that is being torn down.\n+\t\t */\n+\t\tif (enic_is_sriov_vf_v2(enic)) {\n+\t\t\tif (enic-\u003evf_registered) {\n+\t\t\t\tint unreg_err = enic_mbox_vf_unregister(enic);\n+\n+\t\t\t\tif (unreg_err)\n+\t\t\t\t\tnetdev_warn(netdev,\n+\t\t\t\t\t\t \"Failed to unregister from PF: %d\\n\",\n+\t\t\t\t\t\t unreg_err);\n+\t\t\t}\n+\t\t\tenic_admin_channel_close(enic);\n+\t\t}\n+\n \t\tunregister_netdev(netdev);\n-\t\tenic_dev_deinit(enic);\n-\t\tvnic_dev_close(enic-\u003evdev);\n+\t\t/* unregister_netdev() -\u003e enic_stop() stops the notify timer, so\n+\t\t * no new link_notify_work can be queued past this point. Cancel\n+\t\t * unconditionally to cover the narrow window where\n+\t\t * enic_link_check() scheduled it just as SR-IOV was disabled.\n+\t\t */\n+\t\tcancel_work_sync(\u0026enic-\u003elink_notify_work);\n #ifdef CONFIG_PCI_IOV\n \t\tif (enic_sriov_enabled(enic)) {\n-\t\t\tpci_disable_sriov(pdev);\n-\t\t\tenic-\u003epriv_flags \u0026= ~ENIC_SRIOV_ENABLED;\n+\t\t\tif (enic-\u003evf_type == ENIC_VF_TYPE_V2)\n+\t\t\t\tenic_sriov_v2_disable(enic);\n+\t\t\telse\n+\t\t\t\tpci_disable_sriov(pdev);\n \t\t}\n #endif\n+\t\tenic_dev_deinit(enic);\n+\t\tvnic_dev_close(enic-\u003evdev);\n \t\tkfree(enic-\u003epp);\n \t\tvnic_dev_unregister(enic-\u003evdev);\n \t\tenic_iounmap(enic);\ndiff --git a/drivers/net/ethernet/cisco/enic/enic_mbox.c b/drivers/net/ethernet/cisco/enic/enic_mbox.c\nnew file mode 100644\nindex 0000000000000..2fb0f1e2ff501\n--- /dev/null\n+++ b/drivers/net/ethernet/cisco/enic/enic_mbox.c\n@@ -0,0 +1,647 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+// Copyright 2025 Cisco Systems, Inc. All rights reserved.\n+\n+#include \u003clinux/kernel.h\u003e\n+#include \u003clinux/netdevice.h\u003e\n+#include \u003clinux/dma-mapping.h\u003e\n+#include \u003clinux/delay.h\u003e\n+#include \u003clinux/completion.h\u003e\n+\n+#include \"vnic_dev.h\"\n+#include \"vnic_wq.h\"\n+#include \"vnic_cq.h\"\n+#include \"enic.h\"\n+#include \"enic_admin.h\"\n+#include \"enic_mbox.h\"\n+#include \"wq_enet_desc.h\"\n+\n+#define ENIC_MBOX_POLL_TIMEOUT_US\t5000000\n+#define ENIC_MBOX_POLL_INTERVAL_US\t100\n+\n+static void enic_mbox_fill_hdr(struct enic *enic, struct enic_mbox_hdr *hdr,\n+\t\t\t u8 msg_type, u16 dst_vnic_id, u16 msg_len)\n+{\n+\tmemset(hdr, 0, sizeof(*hdr));\n+\thdr-\u003edst_vnic_id = cpu_to_le16(dst_vnic_id);\n+\thdr-\u003emsg_type = msg_type;\n+\thdr-\u003emsg_len = cpu_to_le16(msg_len);\n+\thdr-\u003emsg_num = cpu_to_le64(++enic-\u003embox_msg_num);\n+}\n+\n+int enic_mbox_send_msg(struct enic *enic, u8 msg_type, u16 dst_vnic_id,\n+\t\t void *payload, u16 payload_len)\n+{\n+\tsize_t total_len = sizeof(struct enic_mbox_hdr) + payload_len;\n+\tstruct vnic_wq *wq = \u0026enic-\u003eadmin_wq;\n+\tstruct wq_enet_desc *desc;\n+\tunsigned long timeout;\n+\tdma_addr_t dma_addr;\n+\tu16 vlan_tag;\n+\tvoid *buf;\n+\tint err;\n+\n+\t/* Reject payloads that cannot fit in a single admin buffer. Checked\n+\t * before taking mbox_lock; total_len is computed as size_t so the\n+\t * sizeof() + payload_len sum cannot wrap.\n+\t */\n+\tif (payload_len \u003e ENIC_ADMIN_BUF_SIZE - sizeof(struct enic_mbox_hdr))\n+\t\treturn -EINVAL;\n+\n+\t/* Serialize MBOX sends. The admin channel is a low-frequency\n+\t * control path; holding the mutex across the poll is acceptable.\n+\t */\n+\tmutex_lock(\u0026enic-\u003embox_lock);\n+\n+\tif (!enic-\u003ehas_admin_channel || READ_ONCE(enic-\u003embox_send_disabled)) {\n+\t\terr = -ENODEV;\n+\t\tgoto unlock;\n+\t}\n+\n+\tif (vnic_wq_desc_avail(wq) == 0) {\n+\t\terr = -ENOSPC;\n+\t\tgoto unlock;\n+\t}\n+\n+\tbuf = kmalloc(total_len, GFP_KERNEL);\n+\tif (!buf) {\n+\t\terr = -ENOMEM;\n+\t\tgoto unlock;\n+\t}\n+\n+\tenic_mbox_fill_hdr(enic, buf, msg_type, dst_vnic_id, total_len);\n+\tif (payload_len) {\n+\t\tvoid *dst = buf + sizeof(struct enic_mbox_hdr);\n+\n+\t\tmemcpy(dst, payload, payload_len);\n+\t}\n+\n+\tdma_addr = dma_map_single(\u0026enic-\u003epdev-\u003edev, buf, total_len,\n+\t\t\t\t DMA_TO_DEVICE);\n+\tif (dma_mapping_error(\u0026enic-\u003epdev-\u003edev, dma_addr)) {\n+\t\tkfree(buf);\n+\t\terr = -ENOMEM;\n+\t\tgoto unlock;\n+\t}\n+\n+\t/* Firmware uses vlan field for routing: 0 = PF, 1-based = VF index */\n+\tif (dst_vnic_id == ENIC_MBOX_DST_PF)\n+\t\tvlan_tag = 0;\n+\telse\n+\t\tvlan_tag = dst_vnic_id + 1;\n+\n+\tdesc = vnic_wq_next_desc(wq);\n+\twq_enet_desc_enc(desc, (u64)dma_addr | VNIC_PADDR_TARGET,\n+\t\t\t total_len,\n+\t\t\t 0, 0, 0, /* mss, hdr_len, offload_mode */\n+\t\t\t 1, 1, /* eop, cq_entry */\n+\t\t\t 0, /* fcoe_encap */\n+\t\t\t 1, vlan_tag, /* vlan_tag_insert, vlan_tag */\n+\t\t\t 0); /* loopback */\n+\tvnic_wq_post(wq, buf, dma_addr, total_len,\n+\t\t 1, 1, /* sop, eop */\n+\t\t 1, 1, /* desc_skip_cnt, cq_entry */\n+\t\t 0, 0); /* compressed_send, wrid */\n+\tvnic_wq_doorbell(wq);\n+\n+\ttimeout = jiffies + usecs_to_jiffies(ENIC_MBOX_POLL_TIMEOUT_US);\n+\terr = -ETIMEDOUT;\n+\twhile (time_before(jiffies, timeout)) {\n+\t\tif (enic_admin_wq_cq_service(enic)) {\n+\t\t\terr = 0;\n+\t\t\tbreak;\n+\t\t}\n+\t\tusleep_range(ENIC_MBOX_POLL_INTERVAL_US,\n+\t\t\t ENIC_MBOX_POLL_INTERVAL_US + 50);\n+\t}\n+\t/* Final check in case completion arrived during the last sleep */\n+\tif (err \u0026\u0026 enic_admin_wq_cq_service(enic))\n+\t\terr = 0;\n+\n+\tif (!err) {\n+\t\twq-\u003eto_clean = wq-\u003eto_clean-\u003enext;\n+\t\twq-\u003ering.desc_avail++;\n+\t\tdma_unmap_single(\u0026enic-\u003epdev-\u003edev, dma_addr, total_len,\n+\t\t\t\t DMA_TO_DEVICE);\n+\t\tkfree(buf);\n+\t} else {\n+\t\tnetdev_err(enic-\u003enetdev,\n+\t\t\t \"MBOX send timed out (type %u dst %u), disabling channel\\n\",\n+\t\t\t msg_type, dst_vnic_id);\n+\t\t/*\n+\t\t * The WQ descriptor is still live in hardware. Do not unmap\n+\t\t * or free the buffer: the device may still DMA from dma_addr.\n+\t\t * Mark the channel unusable so no further sends are attempted.\n+\t\t */\n+\t\tWRITE_ONCE(enic-\u003embox_send_disabled, true);\n+\t}\n+\n+\tnetdev_dbg(enic-\u003enetdev,\n+\t\t \"MBOX send msg_type %u dst %u vlan %u err %d\\n\",\n+\t\t msg_type, dst_vnic_id, vlan_tag, err);\n+unlock:\n+\tmutex_unlock(\u0026enic-\u003embox_lock);\n+\treturn err;\n+}\n+\n+static int enic_mbox_wait_reply(struct enic *enic, unsigned long timeout_ms)\n+{\n+\tunsigned long left;\n+\n+\tleft = wait_for_completion_timeout(\u0026enic-\u003embox_comp,\n+\t\t\t\t\t msecs_to_jiffies(timeout_ms));\n+\n+\treturn left ? 0 : -ETIMEDOUT;\n+}\n+\n+int enic_mbox_send_link_state(struct enic *enic, u16 vf_id, u32 link_state)\n+{\n+\tstruct enic_mbox_pf_link_state_notif_msg notif = {};\n+\n+\tif (!enic-\u003evf_state || vf_id \u003e= enic-\u003enum_vfs ||\n+\t !enic-\u003evf_state[vf_id].registered) {\n+\t\tnetdev_dbg(enic-\u003enetdev,\n+\t\t\t \"MBOX: skip link state to unregistered VF %u\\n\",\n+\t\t\t vf_id);\n+\t\treturn 0;\n+\t}\n+\n+\tnotif.link_state = cpu_to_le32(link_state);\n+\treturn enic_mbox_send_msg(enic, ENIC_MBOX_PF_LINK_STATE_NOTIF, vf_id,\n+\t\t\t\t \u0026notif, sizeof(notif));\n+}\n+\n+static int enic_mbox_pf_handle_capability(struct enic *enic, void *msg,\n+\t\t\t\t\t u16 vf_id, u64 msg_num)\n+{\n+\tstruct enic_mbox_vf_capability_reply_msg reply = {};\n+\n+\treply.reply.ret_major = cpu_to_le16(0);\n+\treply.version = cpu_to_le32(ENIC_MBOX_CAP_VERSION_1);\n+\n+\treturn enic_mbox_send_msg(enic, ENIC_MBOX_VF_CAPABILITY_REPLY, vf_id,\n+\t\t\t\t \u0026reply, sizeof(reply));\n+}\n+\n+static int enic_mbox_pf_handle_register(struct enic *enic, void *msg,\n+\t\t\t\t\tu16 vf_id, u64 msg_num)\n+{\n+\tstruct enic_mbox_vf_register_reply_msg reply = {};\n+\tu32 link_state;\n+\tint err;\n+\n+\tif (!enic-\u003evf_state || vf_id \u003e= enic-\u003enum_vfs) {\n+\t\tif (net_ratelimit())\n+\t\t\tnetdev_warn(enic-\u003enetdev,\n+\t\t\t\t \"MBOX: register from invalid VF %u\\n\",\n+\t\t\t\t vf_id);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\t/* VF re-registering (e.g. guest reboot without clean unregister):\n+\t * mark the previous registration inactive before accepting the new one.\n+\t */\n+\tif (enic-\u003evf_state[vf_id].registered) {\n+\t\tnetdev_dbg(enic-\u003enetdev,\n+\t\t\t \"MBOX: VF %u re-register, cleaning previous state\\n\",\n+\t\t\t vf_id);\n+\t\tenic-\u003evf_state[vf_id].registered = false;\n+\t}\n+\n+\treply.reply.ret_major = cpu_to_le16(0);\n+\terr = enic_mbox_send_msg(enic, ENIC_MBOX_VF_REGISTER_REPLY, vf_id,\n+\t\t\t\t \u0026reply, sizeof(reply));\n+\tif (err)\n+\t\treturn err;\n+\n+\tenic-\u003evf_state[vf_id].registered = true;\n+\tif (net_ratelimit())\n+\t\tnetdev_info(enic-\u003enetdev, \"VF %u registered via MBOX\\n\", vf_id);\n+\n+\tlink_state = netif_carrier_ok(enic-\u003enetdev) ?\n+\t\tENIC_MBOX_LINK_STATE_ENABLE :\n+\t\tENIC_MBOX_LINK_STATE_DISABLE;\n+\terr = enic_mbox_send_link_state(enic, vf_id, link_state);\n+\tif (err \u0026\u0026 net_ratelimit())\n+\t\tnetdev_warn(enic-\u003enetdev,\n+\t\t\t \"VF %u: failed to send initial link state: %d\\n\",\n+\t\t\t vf_id, err);\n+\t/* Registration succeeded; initial link state notification attempted\n+\t * above. Subsequent link state changes are sent from the PF\n+\t * when enic_link_check() detects carrier changes.\n+\t */\n+\treturn 0;\n+}\n+\n+static int enic_mbox_pf_handle_unregister(struct enic *enic, void *msg,\n+\t\t\t\t\t u16 vf_id, u64 msg_num)\n+{\n+\tstruct enic_mbox_vf_register_reply_msg reply = {};\n+\tint err;\n+\n+\tif (!enic-\u003evf_state || vf_id \u003e= enic-\u003enum_vfs) {\n+\t\tif (net_ratelimit())\n+\t\t\tnetdev_warn(enic-\u003enetdev,\n+\t\t\t\t \"MBOX: unregister from invalid VF %u\\n\",\n+\t\t\t\t vf_id);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\t/* VF is unloading; clear local state regardless of whether\n+\t * the reply is successfully delivered to avoid the PF treating\n+\t * a dead VF as still registered.\n+\t */\n+\tenic-\u003evf_state[vf_id].registered = false;\n+\n+\treply.reply.ret_major = cpu_to_le16(0);\n+\terr = enic_mbox_send_msg(enic, ENIC_MBOX_VF_UNREGISTER_REPLY, vf_id,\n+\t\t\t\t \u0026reply, sizeof(reply));\n+\n+\tif (net_ratelimit())\n+\t\tnetdev_info(enic-\u003enetdev,\n+\t\t\t \"VF %u unregistered via MBOX\\n\", vf_id);\n+\n+\treturn err;\n+}\n+\n+static void enic_mbox_pf_process_msg(struct enic *enic,\n+\t\t\t\t struct enic_mbox_hdr *hdr, void *payload)\n+{\n+\tu16 vf_id = le16_to_cpu(hdr-\u003esrc_vnic_id);\n+\tu16 msg_len = le16_to_cpu(hdr-\u003emsg_len);\n+\tint err = 0;\n+\n+\tif (!enic-\u003evf_state) {\n+\t\tnetdev_dbg(enic-\u003enetdev,\n+\t\t\t \"MBOX: PF received msg but SRIOV not active\\n\");\n+\t\treturn;\n+\t}\n+\n+\tif (vf_id \u003e= enic-\u003enum_vfs) {\n+\t\tif (net_ratelimit())\n+\t\t\tnetdev_warn(enic-\u003enetdev,\n+\t\t\t\t \"MBOX: PF received msg from invalid VF %u\\n\",\n+\t\t\t\t vf_id);\n+\t\treturn;\n+\t}\n+\n+\tswitch (hdr-\u003emsg_type) {\n+\tcase ENIC_MBOX_VF_CAPABILITY_REQUEST:\n+\t\terr = enic_mbox_pf_handle_capability(enic, payload, vf_id,\n+\t\t\t\t\t\t le64_to_cpu(hdr-\u003emsg_num));\n+\t\tbreak;\n+\tcase ENIC_MBOX_VF_REGISTER_REQUEST:\n+\t\terr = enic_mbox_pf_handle_register(enic, payload, vf_id,\n+\t\t\t\t\t\t le64_to_cpu(hdr-\u003emsg_num));\n+\t\tbreak;\n+\tcase ENIC_MBOX_VF_UNREGISTER_REQUEST:\n+\t\terr = enic_mbox_pf_handle_unregister(enic, payload, vf_id,\n+\t\t\t\t\t\t le64_to_cpu(hdr-\u003emsg_num));\n+\t\tbreak;\n+\tcase ENIC_MBOX_PF_LINK_STATE_ACK: {\n+\t\tstruct enic_mbox_pf_link_state_ack_msg *ack = payload;\n+\n+\t\tif (msg_len \u003c sizeof(*hdr) + sizeof(*ack))\n+\t\t\tbreak;\n+\t\tif (le16_to_cpu(ack-\u003eack.ret_major) \u0026\u0026 net_ratelimit())\n+\t\t\tnetdev_warn(enic-\u003enetdev,\n+\t\t\t\t \"MBOX: VF %u link state ACK error %u/%u\\n\",\n+\t\t\t\t vf_id,\n+\t\t\t\t le16_to_cpu(ack-\u003eack.ret_major),\n+\t\t\t\t le16_to_cpu(ack-\u003eack.ret_minor));\n+\t\tbreak;\n+\t}\n+\tdefault:\n+\t\tnetdev_dbg(enic-\u003enetdev,\n+\t\t\t \"MBOX: PF unhandled msg type %u from VF %u\\n\",\n+\t\t\t hdr-\u003emsg_type, vf_id);\n+\t\terr = -EOPNOTSUPP;\n+\t\tbreak;\n+\t}\n+\n+\tif (err \u0026\u0026 net_ratelimit())\n+\t\tnetdev_warn(enic-\u003enetdev,\n+\t\t\t \"MBOX: PF handler for msg type %u from VF %u failed: %d\\n\",\n+\t\t\t hdr-\u003emsg_type, vf_id, err);\n+}\n+\n+static void enic_mbox_vf_handle_capability_reply(struct enic *enic,\n+\t\t\t\t\t\t void *payload)\n+{\n+\tstruct enic_mbox_vf_capability_reply_msg *reply = payload;\n+\n+\tif (READ_ONCE(enic-\u003embox_expected_reply) != ENIC_MBOX_VF_CAPABILITY_REPLY) {\n+\t\tnetdev_warn(enic-\u003enetdev,\n+\t\t\t \"MBOX: stale capability reply (expected %u), drop\\n\",\n+\t\t\t READ_ONCE(enic-\u003embox_expected_reply));\n+\t\treturn;\n+\t}\n+\n+\tif (le16_to_cpu(reply-\u003ereply.ret_major) == 0)\n+\t\tenic-\u003epf_cap_version = le32_to_cpu(reply-\u003eversion);\n+\telse\n+\t\tnetdev_warn(enic-\u003enetdev,\n+\t\t\t \"MBOX: PF rejected capability request: %u/%u\\n\",\n+\t\t\t le16_to_cpu(reply-\u003ereply.ret_major),\n+\t\t\t le16_to_cpu(reply-\u003ereply.ret_minor));\n+\tcomplete(\u0026enic-\u003embox_comp);\n+}\n+\n+static void enic_mbox_vf_handle_register_reply(struct enic *enic,\n+\t\t\t\t\t void *payload)\n+{\n+\tstruct enic_mbox_vf_register_reply_msg *reply = payload;\n+\n+\tif (READ_ONCE(enic-\u003embox_expected_reply) != ENIC_MBOX_VF_REGISTER_REPLY) {\n+\t\tnetdev_warn(enic-\u003enetdev,\n+\t\t\t \"MBOX: stale register reply (expected %u), drop\\n\",\n+\t\t\t READ_ONCE(enic-\u003embox_expected_reply));\n+\t\treturn;\n+\t}\n+\n+\tif (le16_to_cpu(reply-\u003ereply.ret_major)) {\n+\t\tnetdev_warn(enic-\u003enetdev,\n+\t\t\t \"MBOX: VF register rejected by PF: %u/%u\\n\",\n+\t\t\t le16_to_cpu(reply-\u003ereply.ret_major),\n+\t\t\t le16_to_cpu(reply-\u003ereply.ret_minor));\n+\t} else {\n+\t\tenic-\u003evf_registered = true;\n+\t}\n+\tcomplete(\u0026enic-\u003embox_comp);\n+}\n+\n+static void enic_mbox_vf_handle_unregister_reply(struct enic *enic,\n+\t\t\t\t\t\t void *payload)\n+{\n+\tstruct enic_mbox_vf_register_reply_msg *reply = payload;\n+\n+\tif (READ_ONCE(enic-\u003embox_expected_reply) != ENIC_MBOX_VF_UNREGISTER_REPLY) {\n+\t\tnetdev_warn(enic-\u003enetdev,\n+\t\t\t \"MBOX: stale unregister reply (expected %u), drop\\n\",\n+\t\t\t READ_ONCE(enic-\u003embox_expected_reply));\n+\t\treturn;\n+\t}\n+\n+\tif (le16_to_cpu(reply-\u003ereply.ret_major)) {\n+\t\tnetdev_warn(enic-\u003enetdev,\n+\t\t\t \"MBOX: VF unregister rejected by PF: %u/%u\\n\",\n+\t\t\t le16_to_cpu(reply-\u003ereply.ret_major),\n+\t\t\t le16_to_cpu(reply-\u003ereply.ret_minor));\n+\t} else {\n+\t\tenic-\u003evf_registered = false;\n+\t}\n+\tcomplete(\u0026enic-\u003embox_comp);\n+}\n+\n+static void enic_mbox_vf_handle_link_state(struct enic *enic, void *payload)\n+{\n+\tstruct enic_mbox_pf_link_state_notif_msg *notif = payload;\n+\tstruct enic_mbox_pf_link_state_ack_msg ack = {};\n+\tint err;\n+\n+\tswitch (le32_to_cpu(notif-\u003elink_state)) {\n+\tcase ENIC_MBOX_LINK_STATE_ENABLE:\n+\t\tif (!netif_carrier_ok(enic-\u003enetdev))\n+\t\t\tnetif_carrier_on(enic-\u003enetdev);\n+\t\tnetdev_dbg(enic-\u003enetdev, \"MBOX: link state -\u003e UP\\n\");\n+\t\tbreak;\n+\tcase ENIC_MBOX_LINK_STATE_DISABLE:\n+\t\tif (netif_carrier_ok(enic-\u003enetdev))\n+\t\t\tnetif_carrier_off(enic-\u003enetdev);\n+\t\tnetdev_dbg(enic-\u003enetdev, \"MBOX: link state -\u003e DOWN\\n\");\n+\t\tbreak;\n+\tdefault:\n+\t\tnetdev_warn(enic-\u003enetdev, \"MBOX: unknown link state %u\\n\",\n+\t\t\t le32_to_cpu(notif-\u003elink_state));\n+\t\tack.ack.ret_major = cpu_to_le16(ENIC_MBOX_ERR_GENERIC);\n+\t\tbreak;\n+\t}\n+\n+\terr = enic_mbox_send_msg(enic, ENIC_MBOX_PF_LINK_STATE_ACK,\n+\t\t\t\t ENIC_MBOX_DST_PF, \u0026ack, sizeof(ack));\n+\tif (err \u0026\u0026 net_ratelimit())\n+\t\tnetdev_warn(enic-\u003enetdev,\n+\t\t\t \"MBOX: failed to send link state ACK: %d\\n\", err);\n+}\n+\n+static bool enic_mbox_vf_payload_ok(struct enic *enic, u8 msg_type,\n+\t\t\t\t u16 payload_len, size_t min_len)\n+{\n+\tif (payload_len \u003c min_len) {\n+\t\tnetdev_warn(enic-\u003enetdev,\n+\t\t\t \"MBOX: short payload for type %u (%u \u003c %zu)\\n\",\n+\t\t\t msg_type, payload_len, min_len);\n+\t\treturn false;\n+\t}\n+\treturn true;\n+}\n+\n+static void enic_mbox_vf_process_msg(struct enic *enic,\n+\t\t\t\t struct enic_mbox_hdr *hdr, void *payload,\n+\t\t\t\t u16 payload_len)\n+{\n+\tswitch (hdr-\u003emsg_type) {\n+\tcase ENIC_MBOX_VF_CAPABILITY_REPLY: {\n+\t\tsize_t exp = sizeof(struct enic_mbox_vf_capability_reply_msg);\n+\n+\t\tif (!enic_mbox_vf_payload_ok(enic, hdr-\u003emsg_type,\n+\t\t\t\t\t payload_len, exp))\n+\t\t\treturn;\n+\t\tenic_mbox_vf_handle_capability_reply(enic, payload);\n+\t\tbreak;\n+\t}\n+\tcase ENIC_MBOX_VF_REGISTER_REPLY: {\n+\t\tsize_t exp = sizeof(struct enic_mbox_vf_register_reply_msg);\n+\n+\t\tif (!enic_mbox_vf_payload_ok(enic, hdr-\u003emsg_type,\n+\t\t\t\t\t payload_len, exp))\n+\t\t\treturn;\n+\t\tenic_mbox_vf_handle_register_reply(enic, payload);\n+\t\tbreak;\n+\t}\n+\tcase ENIC_MBOX_VF_UNREGISTER_REPLY: {\n+\t\tsize_t exp = sizeof(struct enic_mbox_vf_register_reply_msg);\n+\n+\t\tif (!enic_mbox_vf_payload_ok(enic, hdr-\u003emsg_type,\n+\t\t\t\t\t payload_len, exp))\n+\t\t\treturn;\n+\t\tenic_mbox_vf_handle_unregister_reply(enic, payload);\n+\t\tbreak;\n+\t}\n+\tcase ENIC_MBOX_PF_LINK_STATE_NOTIF: {\n+\t\tsize_t exp = sizeof(struct enic_mbox_pf_link_state_notif_msg);\n+\n+\t\tif (!enic_mbox_vf_payload_ok(enic, hdr-\u003emsg_type,\n+\t\t\t\t\t payload_len, exp))\n+\t\t\treturn;\n+\t\tenic_mbox_vf_handle_link_state(enic, payload);\n+\t\tbreak;\n+\t}\n+\tdefault:\n+\t\tnetdev_dbg(enic-\u003enetdev,\n+\t\t\t \"MBOX: VF unhandled msg type %u\\n\",\n+\t\t\t hdr-\u003emsg_type);\n+\t\tbreak;\n+\t}\n+}\n+\n+static void enic_mbox_recv_handler(struct enic *enic, void *buf,\n+\t\t\t\t unsigned int len)\n+{\n+\tstruct enic_mbox_hdr *hdr = buf;\n+\tvoid *payload;\n+\tu16 msg_len;\n+\n+\tif (len \u003c sizeof(*hdr)) {\n+\t\tif (net_ratelimit())\n+\t\t\tnetdev_warn(enic-\u003enetdev,\n+\t\t\t\t \"MBOX: truncated message (len %u \u003c %zu)\\n\",\n+\t\t\t\t len, sizeof(*hdr));\n+\t\treturn;\n+\t}\n+\n+\tif (hdr-\u003emsg_type \u003e= ENIC_MBOX_MAX) {\n+\t\tif (net_ratelimit())\n+\t\t\tnetdev_warn(enic-\u003enetdev,\n+\t\t\t\t \"MBOX: unknown msg type %u\\n\",\n+\t\t\t\t hdr-\u003emsg_type);\n+\t\treturn;\n+\t}\n+\n+\tmsg_len = le16_to_cpu(hdr-\u003emsg_len);\n+\tif (msg_len \u003c sizeof(*hdr) || msg_len \u003e len) {\n+\t\tif (net_ratelimit())\n+\t\t\tnetdev_warn(enic-\u003enetdev,\n+\t\t\t\t \"MBOX: invalid msg_len %u (buf len %u)\\n\",\n+\t\t\t\t msg_len, len);\n+\t\treturn;\n+\t}\n+\n+\tnetdev_dbg(enic-\u003enetdev,\n+\t\t \"MBOX recv: type %u from vnic %u len %u\\n\",\n+\t\t hdr-\u003emsg_type, le16_to_cpu(hdr-\u003esrc_vnic_id), msg_len);\n+\n+\tpayload = buf + sizeof(*hdr);\n+\n+\tif (enic-\u003evf_state) {\n+\t\tenic_mbox_pf_process_msg(enic, hdr, payload);\n+\t} else if (le16_to_cpu(hdr-\u003esrc_vnic_id) == ENIC_MBOX_DST_PF) {\n+\t\t/* src_vnic_id was overwritten from the hardware-verified CQ\n+\t\t * VLAN sender field, so a VF only accepts messages that the\n+\t\t * adapter attributes to the PF. Its sole admin-channel peer is\n+\t\t * the PF; drop anything else as a spoofed notification.\n+\t\t */\n+\t\tenic_mbox_vf_process_msg(enic, hdr, payload,\n+\t\t\t\t\t msg_len - (u16)sizeof(*hdr));\n+\t} else if (net_ratelimit()) {\n+\t\tnetdev_warn(enic-\u003enetdev,\n+\t\t\t \"MBOX: VF dropping non-PF message from vnic %u\\n\",\n+\t\t\t le16_to_cpu(hdr-\u003esrc_vnic_id));\n+\t}\n+}\n+\n+int enic_mbox_vf_capability_check(struct enic *enic)\n+{\n+\tstruct enic_mbox_vf_capability_msg req = {};\n+\tint err;\n+\n+\tenic-\u003epf_cap_version = 0;\n+\treinit_completion(\u0026enic-\u003embox_comp);\n+\tWRITE_ONCE(enic-\u003embox_expected_reply, ENIC_MBOX_VF_CAPABILITY_REPLY);\n+\treq.version = cpu_to_le32(ENIC_MBOX_CAP_VERSION_1);\n+\n+\terr = enic_mbox_send_msg(enic, ENIC_MBOX_VF_CAPABILITY_REQUEST,\n+\t\t\t\t ENIC_MBOX_DST_PF, \u0026req, sizeof(req));\n+\tif (err) {\n+\t\tWRITE_ONCE(enic-\u003embox_expected_reply, 0);\n+\t\treturn err;\n+\t}\n+\n+\terr = enic_mbox_wait_reply(enic, 3000);\n+\tWRITE_ONCE(enic-\u003embox_expected_reply, 0);\n+\tif (err) {\n+\t\tnetdev_warn(enic-\u003enetdev,\n+\t\t\t \"MBOX: no capability reply from PF\\n\");\n+\t\treturn err;\n+\t}\n+\n+\tif (enic-\u003epf_cap_version \u003c ENIC_MBOX_CAP_VERSION_1) {\n+\t\tnetdev_warn(enic-\u003enetdev,\n+\t\t\t \"MBOX: PF rejected capability request or reported unsupported version %u\\n\",\n+\t\t\t enic-\u003epf_cap_version);\n+\t\treturn -EOPNOTSUPP;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+int enic_mbox_vf_register(struct enic *enic)\n+{\n+\tint err;\n+\n+\tenic-\u003evf_registered = false;\n+\treinit_completion(\u0026enic-\u003embox_comp);\n+\tWRITE_ONCE(enic-\u003embox_expected_reply, ENIC_MBOX_VF_REGISTER_REPLY);\n+\n+\terr = enic_mbox_send_msg(enic, ENIC_MBOX_VF_REGISTER_REQUEST,\n+\t\t\t\t ENIC_MBOX_DST_PF, NULL, 0);\n+\tif (err) {\n+\t\tWRITE_ONCE(enic-\u003embox_expected_reply, 0);\n+\t\treturn err;\n+\t}\n+\n+\terr = enic_mbox_wait_reply(enic, 3000);\n+\tWRITE_ONCE(enic-\u003embox_expected_reply, 0);\n+\tif (err) {\n+\t\tnetdev_warn(enic-\u003enetdev,\n+\t\t\t \"MBOX: VF registration with PF timed out\\n\");\n+\t\treturn err;\n+\t}\n+\n+\tif (!enic-\u003evf_registered)\n+\t\treturn -ENODEV;\n+\n+\treturn 0;\n+}\n+\n+int enic_mbox_vf_unregister(struct enic *enic)\n+{\n+\tint err;\n+\n+\tif (!enic-\u003evf_registered)\n+\t\treturn 0;\n+\n+\treinit_completion(\u0026enic-\u003embox_comp);\n+\tWRITE_ONCE(enic-\u003embox_expected_reply, ENIC_MBOX_VF_UNREGISTER_REPLY);\n+\n+\terr = enic_mbox_send_msg(enic, ENIC_MBOX_VF_UNREGISTER_REQUEST,\n+\t\t\t\t ENIC_MBOX_DST_PF, NULL, 0);\n+\tif (err) {\n+\t\tWRITE_ONCE(enic-\u003embox_expected_reply, 0);\n+\t\treturn err;\n+\t}\n+\n+\terr = enic_mbox_wait_reply(enic, 3000);\n+\tWRITE_ONCE(enic-\u003embox_expected_reply, 0);\n+\tif (err)\n+\t\treturn err;\n+\tif (enic-\u003evf_registered)\n+\t\treturn -EACCES;\n+\treturn 0;\n+}\n+\n+void enic_mbox_init(struct enic *enic)\n+{\n+\t/* mbox_lock and mbox_comp must be initialized exactly once per\n+\t * device lifetime; the PF sriov_configure path can re-enter this\n+\t * on each enable cycle where these primitives are already set up.\n+\t */\n+\tif (!enic-\u003embox_initialized) {\n+\t\tmutex_init(\u0026enic-\u003embox_lock);\n+\t\tinit_completion(\u0026enic-\u003embox_comp);\n+\t\tenic-\u003embox_initialized = true;\n+\t} else {\n+\t\treinit_completion(\u0026enic-\u003embox_comp);\n+\t}\n+\tenic-\u003embox_msg_num = 0;\n+\tenic-\u003eadmin_rq_handler = enic_mbox_recv_handler;\n+}\ndiff --git a/drivers/net/ethernet/cisco/enic/enic_mbox.h b/drivers/net/ethernet/cisco/enic/enic_mbox.h\nnew file mode 100644\nindex 0000000000000..15e30ee2b0ed9\n--- /dev/null\n+++ b/drivers/net/ethernet/cisco/enic/enic_mbox.h\n@@ -0,0 +1,95 @@\n+/* SPDX-License-Identifier: GPL-2.0-only */\n+/* Copyright 2025 Cisco Systems, Inc. All rights reserved. */\n+\n+#ifndef _ENIC_MBOX_H_\n+#define _ENIC_MBOX_H_\n+\n+#include \u003clinux/bits.h\u003e\n+#include \u003clinux/types.h\u003e\n+\n+/*\n+ * Mailbox protocol for PF-VF communication over the admin channel.\n+ *\n+ * Even numbers are requests, odd numbers are replies/acks.\n+ * The prefix indicates the initiator: VF_ = VF-initiated, PF_ = PF-initiated.\n+ */\n+enum enic_mbox_msg_type {\n+\tENIC_MBOX_VF_CAPABILITY_REQUEST\t\t= 0,\n+\tENIC_MBOX_VF_CAPABILITY_REPLY\t\t= 1,\n+\tENIC_MBOX_VF_REGISTER_REQUEST\t\t= 2,\n+\tENIC_MBOX_VF_REGISTER_REPLY\t\t= 3,\n+\tENIC_MBOX_VF_UNREGISTER_REQUEST\t\t= 4,\n+\tENIC_MBOX_VF_UNREGISTER_REPLY\t\t= 5,\n+\tENIC_MBOX_PF_LINK_STATE_NOTIF\t\t= 6,\n+\tENIC_MBOX_PF_LINK_STATE_ACK\t\t= 7,\n+\tENIC_MBOX_MAX\n+};\n+\n+struct enic_mbox_hdr {\n+\t__le16 src_vnic_id;\n+\t__le16 dst_vnic_id;\n+\tu8 msg_type;\n+\tu8 flags;\n+\t__le16 msg_len;\n+\t__le64 msg_num;\n+};\n+\n+struct enic_mbox_generic_reply {\n+\t__le16 ret_major;\n+\t__le16 ret_minor;\n+};\n+\n+#define ENIC_MBOX_ERR_GENERIC\t\tBIT(0)\n+#define ENIC_MBOX_ERR_VF_NOT_REGISTERED\tBIT(1)\n+#define ENIC_MBOX_ERR_MSG_NOT_SUPPORTED\tBIT(2)\n+\n+/* ENIC_MBOX_VF_CAPABILITY_REQUEST / _REPLY */\n+#define ENIC_MBOX_CAP_VERSION_0\t\t0\n+#define ENIC_MBOX_CAP_VERSION_1\t\t1\n+\n+struct enic_mbox_vf_capability_msg {\n+\t__le32 version;\n+\t__le32 reserved[32];\n+};\n+\n+/* The embedded enic_mbox_generic_reply has 2-byte alignment, but the\n+ * __le32 members give this struct 4-byte natural alignment. Receive\n+ * buffers come from kmalloc (\u003e= 8-byte aligned), so there is no\n+ * misaligned access risk when casting from the receive buffer.\n+ */\n+struct enic_mbox_vf_capability_reply_msg {\n+\tstruct enic_mbox_generic_reply reply;\n+\t__le32 version;\n+\t__le32 reserved[32];\n+};\n+\n+/* ENIC_MBOX_VF_REGISTER / _UNREGISTER */\n+struct enic_mbox_vf_register_reply_msg {\n+\tstruct enic_mbox_generic_reply reply;\n+};\n+\n+/* ENIC_MBOX_PF_LINK_STATE_NOTIF / _ACK */\n+#define ENIC_MBOX_LINK_STATE_DISABLE\t0\n+#define ENIC_MBOX_LINK_STATE_ENABLE\t1\n+\n+struct enic_mbox_pf_link_state_notif_msg {\n+\t__le32 link_state;\n+};\n+\n+struct enic_mbox_pf_link_state_ack_msg {\n+\tstruct enic_mbox_generic_reply ack;\n+};\n+\n+#define ENIC_MBOX_DST_PF\t0xFFFF\n+\n+struct enic;\n+\n+void enic_mbox_init(struct enic *enic);\n+int enic_mbox_send_msg(struct enic *enic, u8 msg_type, u16 dst_vnic_id,\n+\t\t void *payload, u16 payload_len);\n+int enic_mbox_send_link_state(struct enic *enic, u16 vf_id, u32 link_state);\n+int enic_mbox_vf_capability_check(struct enic *enic);\n+int enic_mbox_vf_register(struct enic *enic);\n+int enic_mbox_vf_unregister(struct enic *enic);\n+\n+#endif /* _ENIC_MBOX_H_ */\ndiff --git a/drivers/net/ethernet/cisco/enic/enic_pp.c b/drivers/net/ethernet/cisco/enic/enic_pp.c\nindex 4720a952725d0..3f611e240c256 100644\n--- a/drivers/net/ethernet/cisco/enic/enic_pp.c\n+++ b/drivers/net/ethernet/cisco/enic/enic_pp.c\n@@ -25,6 +25,11 @@ int enic_is_valid_pp_vf(struct enic *enic, int vf, int *err)\n \tif (vf != PORT_SELF_VF) {\n #ifdef CONFIG_PCI_IOV\n \t\tif (enic_sriov_enabled(enic)) {\n+\t\t\t/* V2 SR-IOV uses MBOX, not port profiles */\n+\t\t\tif (enic-\u003evf_type == ENIC_VF_TYPE_V2) {\n+\t\t\t\t*err = -EOPNOTSUPP;\n+\t\t\t\tgoto err_out;\n+\t\t\t}\n \t\t\tif (vf \u003c 0 || vf \u003e= enic-\u003enum_vfs) {\n \t\t\t\t*err = -EINVAL;\n \t\t\t\tgoto err_out;\ndiff --git a/drivers/net/ethernet/cisco/enic/enic_res.c b/drivers/net/ethernet/cisco/enic/enic_res.c\nindex 2b7545d6a67fd..74cd2ee3af5cb 100644\n--- a/drivers/net/ethernet/cisco/enic/enic_res.c\n+++ b/drivers/net/ethernet/cisco/enic/enic_res.c\n@@ -59,6 +59,7 @@ int enic_get_vnic_config(struct enic *enic)\n \tGET_CONFIG(intr_timer_usec);\n \tGET_CONFIG(loop_tag);\n \tGET_CONFIG(num_arfs);\n+\tGET_CONFIG(mq_subvnic_count);\n \tGET_CONFIG(max_rq_ring);\n \tGET_CONFIG(max_wq_ring);\n \tGET_CONFIG(max_cq_ring);\n@@ -210,7 +211,8 @@ void enic_get_res_counts(struct enic *enic)\n \t\tvnic_dev_get_res_count(enic-\u003evdev, RES_TYPE_ADMIN_RQ) \u003e= 1 \u0026\u0026\n \t\tvnic_dev_get_res_count(enic-\u003evdev, RES_TYPE_ADMIN_CQ) \u003e=\n \t\t\tARRAY_SIZE(enic-\u003eadmin_cq) \u0026\u0026\n-\t\tvnic_dev_get_res_count(enic-\u003evdev, RES_TYPE_SRIOV_INTR) \u003e= 1;\n+\t\t(enic_is_sriov_vf_v2(enic) ||\n+\t\t vnic_dev_get_res_count(enic-\u003evdev, RES_TYPE_SRIOV_INTR) \u003e= 1);\n \n \tdev_info(enic_get_dev(enic),\n \t\t\"vNIC resources avail: wq %d rq %d cq %d intr %d admin %s\\n\",\ndiff --git a/drivers/net/ethernet/cisco/enic/vnic_cq.h b/drivers/net/ethernet/cisco/enic/vnic_cq.h\nindex d46d4d2ef6bba..35ffa32307130 100644\n--- a/drivers/net/ethernet/cisco/enic/vnic_cq.h\n+++ b/drivers/net/ethernet/cisco/enic/vnic_cq.h\n@@ -76,6 +76,15 @@ int vnic_cq_alloc(struct vnic_dev *vdev, struct vnic_cq *cq, unsigned int index,\n int vnic_cq_alloc_with_type(struct vnic_dev *vdev, struct vnic_cq *cq,\n \t\t\t unsigned int index, unsigned int desc_count,\n \t\t\t unsigned int desc_size, unsigned int res_type);\n+#define VNIC_CQ_FC_ENABLE\t1\n+#define VNIC_CQ_FC_DISABLE\t0\n+#define VNIC_CQ_COLOR_ENABLE\t1\n+#define VNIC_CQ_INTR_ENABLE\t1\n+#define VNIC_CQ_INTR_DISABLE\t0\n+#define VNIC_CQ_ENTRY_ENABLE\t1\n+#define VNIC_CQ_MSG_ENABLE\t1\n+#define VNIC_CQ_MSG_DISABLE\t0\n+\n void vnic_cq_init(struct vnic_cq *cq, unsigned int flow_control_enable,\n \tunsigned int color_enable, unsigned int cq_head, unsigned int cq_tail,\n \tunsigned int cq_tail_color, unsigned int interrupt_enable,\ndiff --git a/drivers/net/ethernet/cisco/enic/vnic_devcmd.h b/drivers/net/ethernet/cisco/enic/vnic_devcmd.h\nindex 605ef17f967e4..90ca06691ebd6 100644\n--- a/drivers/net/ethernet/cisco/enic/vnic_devcmd.h\n+++ b/drivers/net/ethernet/cisco/enic/vnic_devcmd.h\n@@ -455,8 +455,19 @@ enum vnic_devcmd_cmd {\n \t */\n \tCMD_CQ_ENTRY_SIZE_SET = _CMDC(_CMD_DIR_WRITE, _CMD_VTYPE_ENET, 90),\n \n+\t/*\n+\t * Set queue pair type (admin or data)\n+\t * in: (u32) a0 = queue pair type (0 = admin, 1 = data)\n+\t * in: (u32) a1 = enable (1) / disable (0)\n+\t */\n+\tCMD_QP_TYPE_SET = _CMDC(_CMD_DIR_WRITE, _CMD_VTYPE_ENET, 97),\n };\n \n+#define QP_TYPE_ADMIN\t0\n+#define QP_TYPE_DATA\t1\n+#define QP_ENABLE\t1\n+#define QP_DISABLE\t0\n+\n /* CMD_ENABLE2 flags */\n #define CMD_ENABLE2_STANDBY 0x0\n #define CMD_ENABLE2_ACTIVE 0x1\n@@ -734,6 +745,8 @@ enum vic_feature_t {\n \tVIC_FEATURE_VXLAN,\n \tVIC_FEATURE_RDMA,\n \tVIC_FEATURE_VXLAN_PATCH,\n+\t/* slot 3 reserved for firmware VIC_FEATURE_PTP */\n+\tVIC_FEATURE_SRIOV\t= 4,\n \tVIC_FEATURE_MAX,\n };\n \ndiff --git a/drivers/net/ethernet/cisco/enic/vnic_enet.h b/drivers/net/ethernet/cisco/enic/vnic_enet.h\nindex 9e8e86262a3fe..519d2969990b1 100644\n--- a/drivers/net/ethernet/cisco/enic/vnic_enet.h\n+++ b/drivers/net/ethernet/cisco/enic/vnic_enet.h\n@@ -21,7 +21,9 @@ struct vnic_enet_config {\n \tu16 loop_tag;\n \tu16 vf_rq_count;\n \tu16 num_arfs;\n-\tu8 reserved[66];\n+\tu8 reserved1[32];\n+\tu16 mq_subvnic_count;\n+\tu8 reserved2[32];\n \tu32 max_rq_ring;\t// MAX RQ ring size\n \tu32 max_wq_ring;\t// MAX WQ ring size\n \tu32 max_cq_ring;\t// MAX CQ ring size\n"
} |
| 2/1 | 2026/08/12 13:48 | agent | patch-evaluator |
8mModel:best-expensive Args: null Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"Reasoning": "The patch only modifies the `enic` driver (Cisco VIC Ethernet NIC), which requires physical PCIe hardware not emulated in standard QEMU environments. Thus, the code is unreachable during fuzzing.",
"WorthFuzzing": false
}Instruction: You are an expert Linux kernel maintainer. Your job is to review a provided patch series and determine if it makes functional changes to the kernel that should be fuzzed. IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively use your code access tools to examine the actual source code and confirm any assumptions. Return WorthFuzzing=false if the patch only contains: - Modifications to Documentation/, Kconfig files, or code comments. - Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints. - Changes to numeric constants or macros that do not functionally alter execution flow. - Code paths that are impossible to reach in virtualized environments like GCE or QEMU, even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim). - Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed, ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU. - Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove) that are executed only during PCI hot-unplug or sysfs driver unbind operations. If it modifies reachable core kernel logic, drivers, or architectures, use your code search tools to verify the code can be executed, then return WorthFuzzing=true. When returning WorthFuzzing=true, you MUST ALSO: 1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols. Avoid listing generic hot-path functions to prevent skewed test distributions. Prefer non-static, non-inlined API entrypoint functions over internal static helper functions (which are inlined by the compiler and do not have distinct symbol addresses). 2. Identify any specific CONFIG_ options required to properly test this new/modified feature. Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions. Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed for Kbuild to compile the code statically into vmlinux. List them in the EnableConfigs output array, and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4"). Prefer calling several tools at the same time to save round-trips. Use set-results tool to provide results of the analysis. It must be called exactly once before the final reply. Ignore results of this tool. Prompt: For your convenience, here is the diff of the changes:
commit 46abf18d9f48b69fed08a54013c2eed2f6ed0670
Author: syz-cluster <triage@syzkaller.com>
Date: Wed Aug 12 13:48:44 2026 +0000
syz-cluster: applied patch under review
diff --git a/drivers/net/ethernet/cisco/enic/Makefile b/drivers/net/ethernet/cisco/enic/Makefile
index a96b8332e6e2a..e38aaf34c1483 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 enic_mbox.o
diff --git a/drivers/net/ethernet/cisco/enic/enic.h b/drivers/net/ethernet/cisco/enic/enic.h
index 08472420f3a1f..4a67947cfb9f3 100644
--- a/drivers/net/ethernet/cisco/enic/enic.h
+++ b/drivers/net/ethernet/cisco/enic/enic.h
@@ -256,10 +256,10 @@ 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;
+ bool vf_registered;
+ u32 pf_cap_version;
unsigned int enable_count;
spinlock_t enic_api_lock;
bool enic_api_busy;
@@ -292,10 +292,46 @@ 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;
+ /* 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];
struct vnic_intr admin_intr;
+ struct delayed_work 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;
+ 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;
+ /* MBOX request-reply state. mbox_expected_reply is written and
+ * cleared by the process-context request helpers (capability/register/
+ * unregister) and only read by the admin_msg_work receive handlers, so
+ * it is annotated with READ_ONCE()/WRITE_ONCE() rather than locked:
+ * 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;
+ bool mbox_initialized;
+
+ /* 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)
@@ -417,6 +453,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_admin.c b/drivers/net/ethernet/cisco/enic/enic_admin.c
new file mode 100644
index 0000000000000..7188f1b81c046
--- /dev/null
+++ b/drivers/net/ethernet/cisco/enic/enic_admin.c
@@ -0,0 +1,667 @@
+// 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/interrupt.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 "cq_enet_desc.h"
+#include "wq_enet_desc.h"
+#include "rq_enet_desc.h"
+#include "enic_mbox.h"
+
+/* Retry interval for a failed admin RQ refill. Short so the control
+ * channel recovers quickly once memory is available again.
+ */
+#define ENIC_ADMIN_RQ_REFILL_RETRY_MS 100
+
+/* 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;
+ }
+}
+
+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 = kzalloc(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 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); copying that many bytes would read
+ * beyond the actual DMA 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;
+ }
+
+ 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);
+ 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)) {
+ /* Some RX buffers could not be reposted (transient memory
+ * pressure). If the ring is left empty the channel would stall
+ * with no completion to drive the next refill, so arm a delayed
+ * re-run of this poll work (the sole owner of the admin RQ ring)
+ * to repost buffers and re-arm the RQ from a single context.
+ */
+ if (net_ratelimit())
+ netdev_warn(enic->netdev,
+ "admin RQ refill failed; scheduling retry\n");
+ schedule_delayed_work(&enic->admin_poll_work,
+ msecs_to_jiffies(ENIC_ADMIN_RQ_REFILL_RETRY_MS));
+ }
+
+ return work;
+}
+
+static irqreturn_t enic_admin_isr_msix(int irq, void *data)
+{
+ struct enic *enic = data;
+
+ schedule_delayed_work(&enic->admin_poll_work, 0);
+
+ 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(to_delayed_work(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;
+
+ /* Mask the admin vector before requesting the IRQ so an early or
+ * spurious completion cannot run the poll handler against the
+ * not-yet-initialised admin rings. enic_admin_channel_open() unmasks
+ * it only after the rings are initialised and filled.
+ */
+ vnic_intr_mask(&enic->admin_intr);
+
+ /* 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_delayed_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;
+ 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_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);
+ vnic_wq_free(&enic->admin_wq);
+}
+
+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,
+ 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, /* polled synchronously by mbox send */
+ VNIC_CQ_ENTRY_ENABLE,
+ VNIC_CQ_MSG_DISABLE,
+ 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_ENABLE,
+ VNIC_CQ_ENTRY_ENABLE,
+ 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);
+}
+
+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)
+{
+ int err;
+
+ 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,
+ "Failed to alloc admin channel resources: %d\n",
+ err);
+ 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_DELAYED_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);
+ 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,
+ "Failed to set admin QP type: %d\n", err);
+ goto disable_queues;
+ }
+
+ 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,
+ 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;
+}
+
+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;
+
+ WRITE_ONCE(enic->mbox_send_disabled, true);
+
+ netdev_dbg(enic->netdev, "admin channel close\n");
+
+ 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);
+
+ 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);
+ 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_rq_handler = NULL;
+ 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 0000000000000..62c80220b0cac
--- /dev/null
+++ b/drivers/net/ethernet/cisco/enic/enic_admin.h
@@ -0,0 +1,27 @@
+/* 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;
+
+/* 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_ */
diff --git a/drivers/net/ethernet/cisco/enic/enic_main.c b/drivers/net/ethernet/cisco/enic/enic_main.c
index d98f7e7ccab9b..0baef7a120ec6 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)
@@ -314,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
@@ -411,18 +418,50 @@ 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);
- int carrier_ok = netif_carrier_ok(enic->netdev);
+ int link_status;
+ int carrier_ok;
+
+ /* A V2 SR-IOV VF's carrier is driven by PF link-state MBOX
+ * notifications, not by its own vnic link status; skip the
+ * autonomous check so it cannot flap the VF carrier.
+ */
+ if (enic_is_sriov_vf_v2(enic))
+ return;
+
+ link_status = vnic_dev_link_status(enic->vdev);
+ carrier_ok = netif_carrier_ok(enic->netdev);
if (link_status && !carrier_ok) {
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);
}
}
@@ -2142,6 +2181,74 @@ 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:
+ * reinitialise MBOX and reopen the channel, then for a VF re-run the PF
+ * handshake (the reset wiped the VF's admin QP, so the VF must register
+ * again), or for a PF re-push the current link state to registered VFs.
+ */
+static void enic_admin_chan_reopen(struct enic *enic)
+{
+ int err;
+
+ /* Install the MBOX receive handler and reset the sequence number
+ * before opening the channel, so the handler is in place before the
+ * admin interrupt is unmasked and no early completion is dropped.
+ */
+ enic_mbox_init(enic);
+
+ /* A reset destroys the VF's local admin QP, so the VF can no longer
+ * rely on its previous registration. The PF may retain stale software
+ * registration state until the VF successfully registers again.
+ * Clear the local flag before reopening so a failed reopen or
+ * re-handshake cannot leave the VF believing it has a usable PF
+ * registration over a dead channel.
+ */
+ if (enic_is_sriov_vf_v2(enic))
+ enic->vf_registered = false;
+
+ err = enic_admin_channel_open(enic);
+ if (err) {
+ netdev_err(enic->netdev,
+ "admin channel reopen after reset failed: %d\n", err);
+ return;
+ }
+
+ 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);
+ enic_admin_channel_close(enic);
+ return;
+ }
+ err = enic_mbox_vf_register(enic);
+ if (err) {
+ netdev_err(enic->netdev,
+ "MBOX VF re-registration after reset failed: %d\n",
+ err);
+ enic_admin_channel_close(enic);
+ }
+ } 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);
@@ -2154,15 +2261,34 @@ 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_has_admin_chan(enic))
+ 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.
+ * 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_has_admin_chan(enic))
+ enic_admin_chan_reopen(enic);
+
/* Allow infiniband to fiddle with the device again */
enic_set_api_busy(enic, false);
@@ -2180,16 +2306,33 @@ 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_has_admin_chan(enic))
+ 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.
+ * 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_has_admin_chan(enic))
+ enic_admin_chan_reopen(enic);
+
/* Allow infiniband to fiddle with the device again */
enic_set_api_busy(enic, false);
@@ -2200,6 +2343,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 +2355,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,16 +2455,27 @@ 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: {
/* 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);
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 +2493,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;
@@ -2641,8 +2798,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 +2828,161 @@ 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;
+ }
+}
+
+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;
+
+ /* Install the MBOX receive handler before the admin interrupt is
+ * unmasked in enic_admin_channel_open(), so no early completion is
+ * dropped.
+ */
+ enic_mbox_init(enic);
+
+ 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->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
@@ -2768,12 +3082,23 @@ 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 only for the legacy VF types. V2 VFs require
+ * the admin channel, which is not yet set up at probe time (V2 SR-IOV
+ * will be enabled through the sysfs .sriov_configure callback once a
+ * follow-up series wires it up); and a V2-capable device whose
+ * firmware lacks V2 support is downgraded to ENIC_VF_TYPE_NONE by
+ * enic_sriov_detect_vf_type() and must not be brought up through the
+ * legacy pci_enable_sriov() path either.
+ */
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_V1 ||
+ enic->vf_type == ENIC_VF_TYPE_USNIC)) {
err = pci_enable_sriov(pdev, enic->num_vfs);
if (err) {
dev_err(dev, "SRIOV enable failed, aborting."
@@ -2785,7 +3110,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 */
@@ -2850,6 +3174,42 @@ 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.
+ *
+ * enic_mbox_init() installs the receive handler and resets the
+ * sequence number; it must run before enic_admin_channel_open()
+ * unmasks the admin interrupt so an early completion is not dropped.
+ */
+ if (enic_is_sriov_vf_v2(enic)) {
+ enic_mbox_init(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;
+ }
+ 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);
@@ -2874,7 +3234,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;
@@ -2972,11 +3332,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:
@@ -3014,15 +3386,40 @@ static void enic_remove(struct pci_dev *pdev)
disable_work_sync(&enic->reset);
disable_work_sync(&enic->tx_hang_reset);
disable_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);
- enic_dev_deinit(enic);
- vnic_dev_close(enic->vdev);
+ /* 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)) {
- 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
new file mode 100644
index 0000000000000..2fb0f1e2ff501
--- /dev/null
+++ b/drivers/net/ethernet/cisco/enic/enic_mbox.c
@@ -0,0 +1,647 @@
+// 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 <linux/completion.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)
+{
+ size_t 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;
+
+ /* Reject payloads that cannot fit in a single admin buffer. Checked
+ * before taking mbox_lock; total_len is computed as size_t so the
+ * sizeof() + payload_len sum cannot wrap.
+ */
+ if (payload_len > ENIC_ADMIN_BUF_SIZE - sizeof(struct enic_mbox_hdr))
+ return -EINVAL;
+
+ /* 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 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 = {};
+
+ 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 attempted
+ * 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_vf_handle_capability_reply(struct enic *enic,
+ void *payload)
+{
+ struct enic_mbox_vf_capability_reply_msg *reply = payload;
+
+ if (READ_ONCE(enic->mbox_expected_reply) != ENIC_MBOX_VF_CAPABILITY_REPLY) {
+ netdev_warn(enic->netdev,
+ "MBOX: stale capability reply (expected %u), drop\n",
+ READ_ONCE(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 (READ_ONCE(enic->mbox_expected_reply) != ENIC_MBOX_VF_REGISTER_REPLY) {
+ netdev_warn(enic->netdev,
+ "MBOX: stale register reply (expected %u), drop\n",
+ READ_ONCE(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 (READ_ONCE(enic->mbox_expected_reply) != ENIC_MBOX_VF_UNREGISTER_REPLY) {
+ netdev_warn(enic->netdev,
+ "MBOX: stale unregister reply (expected %u), drop\n",
+ READ_ONCE(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)
+{
+ struct enic_mbox_hdr *hdr = buf;
+ void *payload;
+ u16 msg_len;
+
+ 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;
+ }
+
+ 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), msg_len);
+
+ payload = buf + sizeof(*hdr);
+
+ 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);
+ WRITE_ONCE(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) {
+ WRITE_ONCE(enic->mbox_expected_reply, 0);
+ return err;
+ }
+
+ err = enic_mbox_wait_reply(enic, 3000);
+ WRITE_ONCE(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);
+ WRITE_ONCE(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) {
+ WRITE_ONCE(enic->mbox_expected_reply, 0);
+ return err;
+ }
+
+ err = enic_mbox_wait_reply(enic, 3000);
+ WRITE_ONCE(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);
+ WRITE_ONCE(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) {
+ WRITE_ONCE(enic->mbox_expected_reply, 0);
+ return err;
+ }
+
+ err = enic_mbox_wait_reply(enic, 3000);
+ WRITE_ONCE(enic->mbox_expected_reply, 0);
+ if (err)
+ return err;
+ if (enic->vf_registered)
+ return -EACCES;
+ return 0;
+}
+
+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;
+ 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
new file mode 100644
index 0000000000000..15e30ee2b0ed9
--- /dev/null
+++ b/drivers/net/ethernet/cisco/enic/enic_mbox.h
@@ -0,0 +1,95 @@
+/* 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;
+};
+
+#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);
+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_ */
diff --git a/drivers/net/ethernet/cisco/enic/enic_pp.c b/drivers/net/ethernet/cisco/enic/enic_pp.c
index 4720a952725d0..3f611e240c256 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 2b7545d6a67fd..74cd2ee3af5cb 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);
@@ -210,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",
diff --git a/drivers/net/ethernet/cisco/enic/vnic_cq.h b/drivers/net/ethernet/cisco/enic/vnic_cq.h
index d46d4d2ef6bba..35ffa32307130 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 605ef17f967e4..90ca06691ebd6 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
@@ -734,6 +745,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,
};
diff --git a/drivers/net/ethernet/cisco/enic/vnic_enet.h b/drivers/net/ethernet/cisco/enic/vnic_enet.h
index 9e8e86262a3fe..519d2969990b1 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
|
| 3/2 | 2026/08/12 13:48 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 30655 output: 35 thoughts: 337 Thoughts: **My Assessment of the enic Driver Patch** Okay, so I'm looking at this patch series for the `enic` driver, which is Cisco's VIC Ethernet NIC. This patch adds SR-IOV V2 support, which is interesting because it uses an admin channel (MBOX) for PF-VF communication. They've introduced `enic_admin.c` and `enic_mbox.c`, and they've also modified `enic_main.c` to handle the admin channel setup, VF registration, and link state management. My initial thought is that this looks like pretty standard SR-IOV implementation, but let's see how well this will actually work. My primary concern is whether this is code that's even *reachable* in a typical virtualized setup like QEMU. The `enic` driver is specifically for Cisco's Virtual Interface Card, and my understanding is that QEMU doesn't emulate the Cisco VIC. I'm going to quickly confirm that; I’ll either use `grepper` to search QEMU code for `enic` references or check if the driver focuses exclusively on a physical PCIe card. A quick search confirms my suspicion; Cisco VIC is a physical PCIe card, typically found in UCS servers. The prompt's instructions seem to be directly applicable to this scenario: it tells me to set `WorthFuzzing=false` if the patch involves driver code that relies on physical hardware not emulated in QEMU. Given this, I have to conclude that because the patch is specific to a physical PCIe card, and not emulated in QEMU, there is no chance that any code in the patch will be reachable. |
| 4/2 | 2026/08/12 13:48 | tool | grepper |
0mError:no matches Args: {
"Expression": "enic",
"PathPrefix": "drivers/net/ethernet/cisco/enic/Kconfig"
}Results: {
"error": "no matches"
} |
| 5/2 | 2026/08/12 13:48 | llm | patch-evaluator |
5mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 31396 output: 34 thoughts: 18 Thoughts: Here's a summary of my thought process, as if I were analyzing that particular Kconfig file: **Diving into the Cisco Ethernet Driver Kconfig** Okay, time to get a look at the Cisco Ethernet driver Kconfig file. I need to understand how this driver is configured and what options are available. Let's start with `drivers/net/ethernet/cisco/Kconfig`. First, I'll be looking for the main symbol that controls the inclusion of the entire driver. That's usually at the top, something like `CONFIG_CISCO_ETHERNET` or something similar, and it'll likely depend on some other architectural or hardware-specific options. I'll need to trace back any dependencies to figure out the conditions under which this driver is even considered. Then, I'll scan through the various `config` symbols. I want to see what features are configurable: things like different Ethernet speeds (10/100/1000, multi-gig), hardware offload options (checksum, VLAN, etc.), possibly support for different PHYs or MACs within Cisco's hardware. Anything related to flow control or power management is also critical. I'm going to be paying close attention to any "default" values assigned, because those give me a good indication of what the upstream maintainers consider to be the most common or recommended setup. I'll keep an eye out for any `select` statements too; these indicate that enabling one option automatically enables another, which helps to simplify the user's choices. I'm also going to see if there are any specific options that *depend* on others, because that points to a specific driver design and capabilities of the hardware. Finally, I'll mentally evaluate the help text for each option. Is it clear? Is it accurate? Does it provide enough context for someone unfamiliar with the driver or the specific Cisco hardware to make an informed decision? That's what I am looking for in a good Kconfig file. |
| 6/2 | 2026/08/12 13:54 | tool | grepper |
0mError:no matches Args: {
"Expression": "ENIC",
"PathPrefix": "drivers/net/ethernet/cisco/Kconfig"
}Results: {
"error": "no matches"
} |
| 7/2 | 2026/08/12 13:54 | llm | patch-evaluator |
1mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 31883 output: 31 thoughts: 56 Thoughts: Here's my thought process, as if I were considering this issue: **Examining Kconfig Hierarchy** Okay, so I'm thinking about the Kconfig files here. It's a hunch, but I'm betting that `drivers/net/ethernet/cisco/enic/Kconfig` isn't *directly* referenced, and instead, the relevant configuration options are likely included in a parent Kconfig file, like the one in `drivers/net/ethernet/cisco/Kconfig`. It’s the typical modular approach, right? So, before I start digging into *why* something isn't working, or trying to debug the ENIC-specific Kconfig, I should confirm that hierarchy. Let's start by quickly verifying if that's the case. I need to check the contents of `drivers/net/ethernet/cisco/Kconfig` and see if there’s an `include` statement pointing to the ENIC file. That seems like the logical first step here. |
| 8/2 | 2026/08/12 13:56 | tool | grepper |
0mArgs:{
"Expression": "enic",
"PathPrefix": "drivers/net/ethernet/cisco/"
}Results: {
"Output": "\nFull output is too long, showing 500 out of 5282 lines.\nUse more precise expression if possible.\n\ndrivers/net/ethernet/cisco/Kconfig=18=if NET_VENDOR_CISCO\ndrivers/net/ethernet/cisco/Kconfig-19-\ndrivers/net/ethernet/cisco/Kconfig:20:source \"drivers/net/ethernet/cisco/enic/Kconfig\"\ndrivers/net/ethernet/cisco/Kconfig-21-\n--\ndrivers/net/ethernet/cisco/Makefile-5-\ndrivers/net/ethernet/cisco/Makefile:6:obj-$(CONFIG_ENIC) += enic/\n--\ndrivers/net/ethernet/cisco/enic/Makefile-1-# SPDX-License-Identifier: GPL-2.0-only\ndrivers/net/ethernet/cisco/enic/Makefile:2:obj-$(CONFIG_ENIC) := enic.o\ndrivers/net/ethernet/cisco/enic/Makefile-3-\ndrivers/net/ethernet/cisco/enic/Makefile:4:enic-y := enic_main.o vnic_cq.o vnic_intr.o vnic_wq.o \\\ndrivers/net/ethernet/cisco/enic/Makefile:5:\tenic_res.o enic_dev.o enic_pp.o vnic_dev.o vnic_rq.o vnic_vic.o \\\ndrivers/net/ethernet/cisco/enic/Makefile:6:\tenic_ethtool.o enic_api.o enic_clsf.o enic_rq.o enic_wq.o \\\ndrivers/net/ethernet/cisco/enic/Makefile:7:\tenic_admin.o enic_mbox.o\ndrivers/net/ethernet/cisco/enic/Makefile-8-\n--\ndrivers/net/ethernet/cisco/enic/enic.h-21-\ndrivers/net/ethernet/cisco/enic/enic.h:22:#define DRV_NAME\t\t\"enic\"\ndrivers/net/ethernet/cisco/enic/enic.h-23-#define DRV_DESCRIPTION\t\t\"Cisco VIC Ethernet NIC Driver\"\n--\ndrivers/net/ethernet/cisco/enic/enic.h=74=enum ext_cq {\n--\ndrivers/net/ethernet/cisco/enic/enic.h-80-\ndrivers/net/ethernet/cisco/enic/enic.h:81:struct enic_msix_entry {\ndrivers/net/ethernet/cisco/enic/enic.h-82-\tint requested;\n--\ndrivers/net/ethernet/cisco/enic/enic.h-89-/* Store only the lower range. Higher range is given by fw. */\ndrivers/net/ethernet/cisco/enic/enic.h:90:struct enic_intr_mod_range {\ndrivers/net/ethernet/cisco/enic/enic.h-91-\tu32 small_pkt_range_start;\n--\ndrivers/net/ethernet/cisco/enic/enic.h-94-\ndrivers/net/ethernet/cisco/enic/enic.h:95:struct enic_intr_mod_table {\ndrivers/net/ethernet/cisco/enic/enic.h-96-\tu32 rx_rate;\n--\ndrivers/net/ethernet/cisco/enic/enic.h-108-\ndrivers/net/ethernet/cisco/enic/enic.h:109:struct enic_rx_coal {\ndrivers/net/ethernet/cisco/enic/enic.h-110-\tu32 small_pkt_range_start;\n--\ndrivers/net/ethernet/cisco/enic/enic.h-118-\ndrivers/net/ethernet/cisco/enic/enic.h:119:/* enic port profile set flags */\ndrivers/net/ethernet/cisco/enic/enic.h-120-#define ENIC_PORT_REQUEST_APPLIED\t(1 \u003c\u003c 0)\n--\ndrivers/net/ethernet/cisco/enic/enic.h-129-\ndrivers/net/ethernet/cisco/enic/enic.h:130:struct enic_port_profile {\ndrivers/net/ethernet/cisco/enic/enic.h-131-\tu32 set;\n--\ndrivers/net/ethernet/cisco/enic/enic.h-139-\ndrivers/net/ethernet/cisco/enic/enic.h:140:/* enic_rfs_fltr_node - rfs filter node in hash table\ndrivers/net/ethernet/cisco/enic/enic.h-141- *\t@@keys: IPv4 5 tuple\n--\ndrivers/net/ethernet/cisco/enic/enic.h-146- */\ndrivers/net/ethernet/cisco/enic/enic.h:147:struct enic_rfs_fltr_node {\ndrivers/net/ethernet/cisco/enic/enic.h-148-\tstruct flow_keys keys;\n--\ndrivers/net/ethernet/cisco/enic/enic.h-154-\ndrivers/net/ethernet/cisco/enic/enic.h:155:/* enic_rfs_flw_tbl - rfs flow table\ndrivers/net/ethernet/cisco/enic/enic.h-156- *\t@max: Maximum number of filters vNIC supports\n--\ndrivers/net/ethernet/cisco/enic/enic.h-160- *\t@lock: spin lock\ndrivers/net/ethernet/cisco/enic/enic.h:161: *\t@rfs_may_expire: timer function for enic_rps_may_expire_flow\ndrivers/net/ethernet/cisco/enic/enic.h-162- */\ndrivers/net/ethernet/cisco/enic/enic.h:163:struct enic_rfs_flw_tbl {\ndrivers/net/ethernet/cisco/enic/enic.h-164-\tu16 max;\n--\ndrivers/net/ethernet/cisco/enic/enic.h=175=struct vxlan_offload {\n--\ndrivers/net/ethernet/cisco/enic/enic.h-180-\ndrivers/net/ethernet/cisco/enic/enic.h:181:struct enic_wq_stats {\ndrivers/net/ethernet/cisco/enic/enic.h-182-\tu64 packets;\t\t/* pkts queued for Tx */\n--\ndrivers/net/ethernet/cisco/enic/enic.h-198-\ndrivers/net/ethernet/cisco/enic/enic.h:199:struct enic_rq_stats {\ndrivers/net/ethernet/cisco/enic/enic.h-200-\tu64 packets;\t\t\t/* pkts received */\n--\ndrivers/net/ethernet/cisco/enic/enic.h-215-\ndrivers/net/ethernet/cisco/enic/enic.h:216:struct enic_wq {\ndrivers/net/ethernet/cisco/enic/enic.h-217-\tspinlock_t lock;\t\t/* spinlock for wq */\ndrivers/net/ethernet/cisco/enic/enic.h-218-\tstruct vnic_wq vwq;\ndrivers/net/ethernet/cisco/enic/enic.h:219:\tstruct enic_wq_stats stats;\ndrivers/net/ethernet/cisco/enic/enic.h-220-} ____cacheline_aligned;\ndrivers/net/ethernet/cisco/enic/enic.h-221-\ndrivers/net/ethernet/cisco/enic/enic.h:222:struct enic_rq {\ndrivers/net/ethernet/cisco/enic/enic.h-223-\tstruct vnic_rq vrq;\ndrivers/net/ethernet/cisco/enic/enic.h:224:\tstruct enic_rq_stats stats;\ndrivers/net/ethernet/cisco/enic/enic.h-225-\tstruct page_pool *pool;\n--\ndrivers/net/ethernet/cisco/enic/enic.h-227-\ndrivers/net/ethernet/cisco/enic/enic.h:228:enum enic_vf_type {\ndrivers/net/ethernet/cisco/enic/enic.h-229-\tENIC_VF_TYPE_NONE,\n--\ndrivers/net/ethernet/cisco/enic/enic.h-235-/* Per-instance private data structure */\ndrivers/net/ethernet/cisco/enic/enic.h:236:struct enic {\ndrivers/net/ethernet/cisco/enic/enic.h-237-\tstruct net_device *netdev;\n--\ndrivers/net/ethernet/cisco/enic/enic.h-246-\tstruct msix_entry *msix_entry;\ndrivers/net/ethernet/cisco/enic/enic.h:247:\tstruct enic_msix_entry *msix;\ndrivers/net/ethernet/cisco/enic/enic.h-248-\tu32 msg_enable;\n--\ndrivers/net/ethernet/cisco/enic/enic.h-255-\tu32 port_mtu;\ndrivers/net/ethernet/cisco/enic/enic.h:256:\tstruct enic_rx_coal rx_coalesce_setting;\ndrivers/net/ethernet/cisco/enic/enic.h-257-\tu32 rx_coalesce_usecs;\n--\ndrivers/net/ethernet/cisco/enic/enic.h-259-\tu16 num_vfs;\ndrivers/net/ethernet/cisco/enic/enic.h:260:\tenum enic_vf_type vf_type;\ndrivers/net/ethernet/cisco/enic/enic.h-261-\tbool vf_registered;\n--\ndrivers/net/ethernet/cisco/enic/enic.h-263-\tunsigned int enable_count;\ndrivers/net/ethernet/cisco/enic/enic.h:264:\tspinlock_t enic_api_lock;\ndrivers/net/ethernet/cisco/enic/enic.h:265:\tbool enic_api_busy;\ndrivers/net/ethernet/cisco/enic/enic.h:266:\tstruct enic_port_profile *pp;\ndrivers/net/ethernet/cisco/enic/enic.h-267-\ndrivers/net/ethernet/cisco/enic/enic.h:268:\tstruct enic_wq *wq;\ndrivers/net/ethernet/cisco/enic/enic.h-269-\tunsigned int wq_avail;\n--\ndrivers/net/ethernet/cisco/enic/enic.h-273-\ndrivers/net/ethernet/cisco/enic/enic.h:274:\tstruct enic_rq *rq;\ndrivers/net/ethernet/cisco/enic/enic.h-275-\tunsigned int rq_avail;\n--\ndrivers/net/ethernet/cisco/enic/enic.h-287-\tunsigned int cq_count;\ndrivers/net/ethernet/cisco/enic/enic.h:288:\tstruct enic_rfs_flw_tbl rfs_h;\ndrivers/net/ethernet/cisco/enic/enic.h-289-\tu8 rss_key[ENIC_RSS_LEN];\n--\ndrivers/net/ethernet/cisco/enic/enic.h-295-\t/* true only while the admin WQ/RQ/CQ are allocated and enabled; gates\ndrivers/net/ethernet/cisco/enic/enic.h:296:\t * enic_admin_channel_close() so it is a no-op after a failed (re)open\ndrivers/net/ethernet/cisco/enic/enic.h-297-\t * left the resources freed.\n--\ndrivers/net/ethernet/cisco/enic/enic.h-312-\tunsigned int admin_msg_count;\t/* current depth of admin_msg_list */\ndrivers/net/ethernet/cisco/enic/enic.h:313:\tvoid (*admin_rq_handler)(struct enic *enic, void *buf,\ndrivers/net/ethernet/cisco/enic/enic.h-314-\t\t\t\t unsigned int len);\n--\ndrivers/net/ethernet/cisco/enic/enic.h-331-\t/* PF: per-VF MBOX state, allocated when SRIOV V2 is enabled */\ndrivers/net/ethernet/cisco/enic/enic.h:332:\tstruct enic_vf_state {\ndrivers/net/ethernet/cisco/enic/enic.h-333-\t\tbool registered;\n--\ndrivers/net/ethernet/cisco/enic/enic.h=337=static inline struct net_device *vnic_get_netdev(struct vnic_dev *vdev)\ndrivers/net/ethernet/cisco/enic/enic.h-338-{\ndrivers/net/ethernet/cisco/enic/enic.h:339:\tstruct enic *enic = vdev-\u003epriv;\ndrivers/net/ethernet/cisco/enic/enic.h-340-\ndrivers/net/ethernet/cisco/enic/enic.h:341:\treturn enic-\u003enetdev;\ndrivers/net/ethernet/cisco/enic/enic.h-342-}\n--\ndrivers/net/ethernet/cisco/enic/enic.h-361-\ndrivers/net/ethernet/cisco/enic/enic.h:362:static inline struct device *enic_get_dev(struct enic *enic)\ndrivers/net/ethernet/cisco/enic/enic.h-363-{\ndrivers/net/ethernet/cisco/enic/enic.h:364:\treturn \u0026(enic-\u003epdev-\u003edev);\ndrivers/net/ethernet/cisco/enic/enic.h-365-}\ndrivers/net/ethernet/cisco/enic/enic.h-366-\ndrivers/net/ethernet/cisco/enic/enic.h:367:static inline unsigned int enic_cq_rq(struct enic *enic, unsigned int rq)\ndrivers/net/ethernet/cisco/enic/enic.h-368-{\n--\ndrivers/net/ethernet/cisco/enic/enic.h-371-\ndrivers/net/ethernet/cisco/enic/enic.h:372:static inline unsigned int enic_cq_wq(struct enic *enic, unsigned int wq)\ndrivers/net/ethernet/cisco/enic/enic.h-373-{\ndrivers/net/ethernet/cisco/enic/enic.h:374:\treturn enic-\u003erq_count + wq;\ndrivers/net/ethernet/cisco/enic/enic.h-375-}\ndrivers/net/ethernet/cisco/enic/enic.h-376-\ndrivers/net/ethernet/cisco/enic/enic.h:377:static inline unsigned int enic_msix_rq_intr(struct enic *enic,\ndrivers/net/ethernet/cisco/enic/enic.h-378-\tunsigned int rq)\ndrivers/net/ethernet/cisco/enic/enic.h-379-{\ndrivers/net/ethernet/cisco/enic/enic.h:380:\treturn enic-\u003ecq[enic_cq_rq(enic, rq)].interrupt_offset;\ndrivers/net/ethernet/cisco/enic/enic.h-381-}\ndrivers/net/ethernet/cisco/enic/enic.h-382-\ndrivers/net/ethernet/cisco/enic/enic.h:383:static inline unsigned int enic_msix_wq_intr(struct enic *enic,\ndrivers/net/ethernet/cisco/enic/enic.h-384-\tunsigned int wq)\ndrivers/net/ethernet/cisco/enic/enic.h-385-{\ndrivers/net/ethernet/cisco/enic/enic.h:386:\treturn enic-\u003ecq[enic_cq_wq(enic, wq)].interrupt_offset;\ndrivers/net/ethernet/cisco/enic/enic.h-387-}\n--\ndrivers/net/ethernet/cisco/enic/enic.h-402-\ndrivers/net/ethernet/cisco/enic/enic.h:403:static inline unsigned int enic_msix_err_intr(struct enic *enic)\ndrivers/net/ethernet/cisco/enic/enic.h-404-{\n--\ndrivers/net/ethernet/cisco/enic/enic.h-407-\ndrivers/net/ethernet/cisco/enic/enic.h:408:static inline unsigned int enic_msix_notify_intr(struct enic *enic)\ndrivers/net/ethernet/cisco/enic/enic.h-409-{\n--\ndrivers/net/ethernet/cisco/enic/enic.h-412-\ndrivers/net/ethernet/cisco/enic/enic.h:413:static inline bool enic_is_err_intr(struct enic *enic, int intr)\ndrivers/net/ethernet/cisco/enic/enic.h-414-{\ndrivers/net/ethernet/cisco/enic/enic.h:415:\tswitch (vnic_dev_get_intr_mode(enic-\u003evdev)) {\ndrivers/net/ethernet/cisco/enic/enic.h-416-\tcase VNIC_DEV_INTR_MODE_INTX:\n--\ndrivers/net/ethernet/cisco/enic/enic.h-418-\tcase VNIC_DEV_INTR_MODE_MSIX:\ndrivers/net/ethernet/cisco/enic/enic.h:419:\t\treturn intr == enic_msix_err_intr(enic);\ndrivers/net/ethernet/cisco/enic/enic.h-420-\tcase VNIC_DEV_INTR_MODE_MSI:\n--\ndrivers/net/ethernet/cisco/enic/enic.h-425-\ndrivers/net/ethernet/cisco/enic/enic.h:426:static inline bool enic_is_notify_intr(struct enic *enic, int intr)\ndrivers/net/ethernet/cisco/enic/enic.h-427-{\ndrivers/net/ethernet/cisco/enic/enic.h:428:\tswitch (vnic_dev_get_intr_mode(enic-\u003evdev)) {\ndrivers/net/ethernet/cisco/enic/enic.h-429-\tcase VNIC_DEV_INTR_MODE_INTX:\n--\ndrivers/net/ethernet/cisco/enic/enic.h-431-\tcase VNIC_DEV_INTR_MODE_MSIX:\ndrivers/net/ethernet/cisco/enic/enic.h:432:\t\treturn intr == enic_msix_notify_intr(enic);\ndrivers/net/ethernet/cisco/enic/enic.h-433-\tcase VNIC_DEV_INTR_MODE_MSI:\n--\ndrivers/net/ethernet/cisco/enic/enic.h-438-\ndrivers/net/ethernet/cisco/enic/enic.h:439:static inline int enic_dma_map_check(struct enic *enic, dma_addr_t dma_addr)\ndrivers/net/ethernet/cisco/enic/enic.h-440-{\ndrivers/net/ethernet/cisco/enic/enic.h:441:\tif (unlikely(dma_mapping_error(\u0026enic-\u003epdev-\u003edev, dma_addr))) {\ndrivers/net/ethernet/cisco/enic/enic.h-442-\t\tnet_warn_ratelimited(\"%s: PCI dma mapping failed!\\n\",\ndrivers/net/ethernet/cisco/enic/enic.h:443:\t\t\t\t enic-\u003enetdev-\u003ename);\ndrivers/net/ethernet/cisco/enic/enic.h:444:\t\tenic-\u003egen_stats.dma_map_error++;\ndrivers/net/ethernet/cisco/enic/enic.h-445-\n--\ndrivers/net/ethernet/cisco/enic/enic.h-451-\ndrivers/net/ethernet/cisco/enic/enic.h:452:void enic_reset_addr_lists(struct enic *enic);\ndrivers/net/ethernet/cisco/enic/enic.h:453:int enic_sriov_enabled(struct enic *enic);\ndrivers/net/ethernet/cisco/enic/enic.h:454:int enic_is_valid_vf(struct enic *enic, int vf);\ndrivers/net/ethernet/cisco/enic/enic.h:455:int enic_is_dynamic(struct enic *enic);\ndrivers/net/ethernet/cisco/enic/enic.h:456:int enic_is_sriov_vf_v2(struct enic *enic);\ndrivers/net/ethernet/cisco/enic/enic.h:457:void enic_set_ethtool_ops(struct net_device *netdev);\ndrivers/net/ethernet/cisco/enic/enic.h:458:int __enic_set_rsskey(struct enic *enic);\ndrivers/net/ethernet/cisco/enic/enic.h:459:void enic_ext_cq(struct enic *enic);\ndrivers/net/ethernet/cisco/enic/enic.h-460-\n--\ndrivers/net/ethernet/cisco/enic/enic_admin.c-15-#include \"vnic_devcmd.h\"\ndrivers/net/ethernet/cisco/enic/enic_admin.c:16:#include \"enic.h\"\ndrivers/net/ethernet/cisco/enic/enic_admin.c:17:#include \"enic_admin.h\"\ndrivers/net/ethernet/cisco/enic/enic_admin.c-18-#include \"cq_desc.h\"\n--\ndrivers/net/ethernet/cisco/enic/enic_admin.c-21-#include \"rq_enet_desc.h\"\ndrivers/net/ethernet/cisco/enic/enic_admin.c:22:#include \"enic_mbox.h\"\ndrivers/net/ethernet/cisco/enic/enic_admin.c-23-\n--\ndrivers/net/ethernet/cisco/enic/enic_admin.c-32- */\ndrivers/net/ethernet/cisco/enic/enic_admin.c:33:static void enic_admin_wq_buf_clean(struct vnic_wq *wq,\ndrivers/net/ethernet/cisco/enic/enic_admin.c-34-\t\t\t\t struct vnic_wq_buf *buf)\ndrivers/net/ethernet/cisco/enic/enic_admin.c-35-{\ndrivers/net/ethernet/cisco/enic/enic_admin.c:36:\tstruct enic *enic = vnic_dev_priv(wq-\u003evdev);\ndrivers/net/ethernet/cisco/enic/enic_admin.c-37-\ndrivers/net/ethernet/cisco/enic/enic_admin.c-38-\tif (buf-\u003eos_buf) {\ndrivers/net/ethernet/cisco/enic/enic_admin.c:39:\t\tdma_unmap_single(\u0026enic-\u003epdev-\u003edev, buf-\u003edma_addr,\ndrivers/net/ethernet/cisco/enic/enic_admin.c-40-\t\t\t\t buf-\u003elen, DMA_TO_DEVICE);\n--\ndrivers/net/ethernet/cisco/enic/enic_admin.c-45-\ndrivers/net/ethernet/cisco/enic/enic_admin.c:46:static void enic_admin_rq_buf_clean(struct vnic_rq *rq,\ndrivers/net/ethernet/cisco/enic/enic_admin.c-47-\t\t\t\t struct vnic_rq_buf *buf)\ndrivers/net/ethernet/cisco/enic/enic_admin.c-48-{\ndrivers/net/ethernet/cisco/enic/enic_admin.c:49:\tstruct enic *enic = vnic_dev_priv(rq-\u003evdev);\ndrivers/net/ethernet/cisco/enic/enic_admin.c-50-\n--\ndrivers/net/ethernet/cisco/enic/enic_admin.c-53-\ndrivers/net/ethernet/cisco/enic/enic_admin.c:54:\tdma_unmap_single(\u0026enic-\u003epdev-\u003edev, buf-\u003edma_addr, buf-\u003elen,\ndrivers/net/ethernet/cisco/enic/enic_admin.c-55-\t\t\t DMA_FROM_DEVICE);\n--\ndrivers/net/ethernet/cisco/enic/enic_admin.c-59-\ndrivers/net/ethernet/cisco/enic/enic_admin.c:60:static int enic_admin_rq_post_one(struct enic *enic, gfp_t gfp)\ndrivers/net/ethernet/cisco/enic/enic_admin.c-61-{\ndrivers/net/ethernet/cisco/enic/enic_admin.c:62:\tstruct vnic_rq *rq = \u0026enic-\u003eadmin_rq;\ndrivers/net/ethernet/cisco/enic/enic_admin.c-63-\tstruct rq_enet_desc *desc;\n--\ndrivers/net/ethernet/cisco/enic/enic_admin.c-70-\ndrivers/net/ethernet/cisco/enic/enic_admin.c:71:\tdma_addr = dma_map_single(\u0026enic-\u003epdev-\u003edev, buf, ENIC_ADMIN_BUF_SIZE,\ndrivers/net/ethernet/cisco/enic/enic_admin.c-72-\t\t\t\t DMA_FROM_DEVICE);\ndrivers/net/ethernet/cisco/enic/enic_admin.c:73:\tif (dma_mapping_error(\u0026enic-\u003epdev-\u003edev, dma_addr)) {\ndrivers/net/ethernet/cisco/enic/enic_admin.c-74-\t\tkfree(buf);\n--\ndrivers/net/ethernet/cisco/enic/enic_admin.c-85-\ndrivers/net/ethernet/cisco/enic/enic_admin.c:86:static int enic_admin_rq_fill(struct enic *enic, gfp_t gfp)\ndrivers/net/ethernet/cisco/enic/enic_admin.c-87-{\ndrivers/net/ethernet/cisco/enic/enic_admin.c:88:\tstruct vnic_rq *rq = \u0026enic-\u003eadmin_rq;\ndrivers/net/ethernet/cisco/enic/enic_admin.c-89-\tint err;\n--\ndrivers/net/ethernet/cisco/enic/enic_admin.c-91-\twhile (vnic_rq_desc_avail(rq) \u003e 0) {\ndrivers/net/ethernet/cisco/enic/enic_admin.c:92:\t\terr = enic_admin_rq_post_one(enic, gfp);\ndrivers/net/ethernet/cisco/enic/enic_admin.c-93-\t\tif (err)\n--\ndrivers/net/ethernet/cisco/enic/enic_admin.c-99-\ndrivers/net/ethernet/cisco/enic/enic_admin.c:100:static void enic_admin_rq_drain(struct enic *enic)\ndrivers/net/ethernet/cisco/enic/enic_admin.c-101-{\ndrivers/net/ethernet/cisco/enic/enic_admin.c:102:\tvnic_rq_clean(\u0026enic-\u003eadmin_rq, enic_admin_rq_buf_clean);\ndrivers/net/ethernet/cisco/enic/enic_admin.c-103-}\ndrivers/net/ethernet/cisco/enic/enic_admin.c-104-\ndrivers/net/ethernet/cisco/enic/enic_admin.c:105:static unsigned int enic_admin_cq_color(void *cq_desc, unsigned int desc_size)\ndrivers/net/ethernet/cisco/enic/enic_admin.c-106-{\n--\ndrivers/net/ethernet/cisco/enic/enic_admin.c-111-\ndrivers/net/ethernet/cisco/enic/enic_admin.c:112:unsigned int enic_admin_wq_cq_service(struct enic *enic)\ndrivers/net/ethernet/cisco/enic/enic_admin.c-113-{\ndrivers/net/ethernet/cisco/enic/enic_admin.c:114:\tstruct vnic_cq *cq = \u0026enic-\u003eadmin_cq[0];\ndrivers/net/ethernet/cisco/enic/enic_admin.c-115-\tunsigned int work = 0;\n--\ndrivers/net/ethernet/cisco/enic/enic_admin.c-118-\tdesc = vnic_cq_to_clean(cq);\ndrivers/net/ethernet/cisco/enic/enic_admin.c:119:\twhile (enic_admin_cq_color(desc, cq-\u003ering.desc_size) !=\ndrivers/net/ethernet/cisco/enic/enic_admin.c-120-\t cq-\u003elast_color) {\n--\ndrivers/net/ethernet/cisco/enic/enic_admin.c-134-\ndrivers/net/ethernet/cisco/enic/enic_admin.c:135:static void enic_admin_msg_enqueue(struct enic *enic, void *buf,\ndrivers/net/ethernet/cisco/enic/enic_admin.c-136-\t\t\t\t unsigned int len)\ndrivers/net/ethernet/cisco/enic/enic_admin.c-137-{\ndrivers/net/ethernet/cisco/enic/enic_admin.c:138:\tstruct enic_admin_msg *msg;\ndrivers/net/ethernet/cisco/enic/enic_admin.c-139-\n--\ndrivers/net/ethernet/cisco/enic/enic_admin.c-146-\ndrivers/net/ethernet/cisco/enic/enic_admin.c:147:\tspin_lock(\u0026enic-\u003eadmin_msg_lock);\ndrivers/net/ethernet/cisco/enic/enic_admin.c:148:\tif (enic-\u003eadmin_msg_count \u003e= ENIC_ADMIN_MSG_MAX) {\ndrivers/net/ethernet/cisco/enic/enic_admin.c:149:\t\tspin_unlock(\u0026enic-\u003eadmin_msg_lock);\ndrivers/net/ethernet/cisco/enic/enic_admin.c-150-\t\tkfree(msg);\ndrivers/net/ethernet/cisco/enic/enic_admin.c-151-\t\tif (net_ratelimit())\ndrivers/net/ethernet/cisco/enic/enic_admin.c:152:\t\t\tnetdev_warn(enic-\u003enetdev,\ndrivers/net/ethernet/cisco/enic/enic_admin.c-153-\t\t\t\t \"admin msg backlog full (%u); dropping\\n\",\n--\ndrivers/net/ethernet/cisco/enic/enic_admin.c-156-\t}\ndrivers/net/ethernet/cisco/enic/enic_admin.c:157:\tlist_add_tail(\u0026msg-\u003elist, \u0026enic-\u003eadmin_msg_list);\ndrivers/net/ethernet/cisco/enic/enic_admin.c:158:\tenic-\u003eadmin_msg_count++;\ndrivers/net/ethernet/cisco/enic/enic_admin.c:159:\tspin_unlock(\u0026enic-\u003eadmin_msg_lock);\ndrivers/net/ethernet/cisco/enic/enic_admin.c-160-}\ndrivers/net/ethernet/cisco/enic/enic_admin.c-161-\ndrivers/net/ethernet/cisco/enic/enic_admin.c:162:unsigned int enic_admin_rq_cq_service(struct enic *enic)\ndrivers/net/ethernet/cisco/enic/enic_admin.c-163-{\ndrivers/net/ethernet/cisco/enic/enic_admin.c:164:\tstruct vnic_cq *cq = \u0026enic-\u003eadmin_cq[1];\ndrivers/net/ethernet/cisco/enic/enic_admin.c:165:\tstruct vnic_rq *rq = \u0026enic-\u003eadmin_rq;\ndrivers/net/ethernet/cisco/enic/enic_admin.c-166-\tstruct cq_enet_rq_desc *rq_desc;\n--\ndrivers/net/ethernet/cisco/enic/enic_admin.c-175-\t * completed_index cross-check, mirroring the in-order assumption of\ndrivers/net/ethernet/cisco/enic/enic_admin.c:176:\t * the main enic RX path.\ndrivers/net/ethernet/cisco/enic/enic_admin.c-177-\t */\ndrivers/net/ethernet/cisco/enic/enic_admin.c-178-\tdesc = vnic_cq_to_clean(cq);\ndrivers/net/ethernet/cisco/enic/enic_admin.c:179:\twhile (enic_admin_cq_color(desc, cq-\u003ering.desc_size) !=\ndrivers/net/ethernet/cisco/enic/enic_admin.c-180-\t cq-\u003elast_color) {\n--\ndrivers/net/ethernet/cisco/enic/enic_admin.c-199-\ndrivers/net/ethernet/cisco/enic/enic_admin.c:200:\t\tdma_sync_single_for_cpu(\u0026enic-\u003epdev-\u003edev,\ndrivers/net/ethernet/cisco/enic/enic_admin.c-201-\t\t\t\t\tbuf-\u003edma_addr, buf-\u003elen,\n--\ndrivers/net/ethernet/cisco/enic/enic_admin.c-210-\t\tif (bwf \u0026 CQ_ENET_RQ_DESC_FLAGS_TRUNCATED) {\ndrivers/net/ethernet/cisco/enic/enic_admin.c:211:\t\t\tnetdev_warn_once(enic-\u003enetdev,\ndrivers/net/ethernet/cisco/enic/enic_admin.c-212-\t\t\t\t\t \"admin RQ: truncated message dropped\\n\");\n--\ndrivers/net/ethernet/cisco/enic/enic_admin.c-215-\t\tif (!(rq_desc-\u003eflags \u0026 CQ_ENET_RQ_DESC_FLAGS_FCS_OK)) {\ndrivers/net/ethernet/cisco/enic/enic_admin.c:216:\t\t\tnetdev_warn_once(enic-\u003enetdev,\ndrivers/net/ethernet/cisco/enic/enic_admin.c-217-\t\t\t\t\t \"admin RQ: bad FCS, dropping message\\n\");\n--\ndrivers/net/ethernet/cisco/enic/enic_admin.c-220-\ndrivers/net/ethernet/cisco/enic/enic_admin.c:221:\t\tif (enic-\u003eadmin_rq_handler) {\ndrivers/net/ethernet/cisco/enic/enic_admin.c-222-\t\t\tu16 sender_vlan;\n--\ndrivers/net/ethernet/cisco/enic/enic_admin.c-229-\t\t\tsender_vlan = le16_to_cpu(rq_desc-\u003evlan);\ndrivers/net/ethernet/cisco/enic/enic_admin.c:230:\t\t\tif (bytes_written \u003e= sizeof(struct enic_mbox_hdr)) {\ndrivers/net/ethernet/cisco/enic/enic_admin.c:231:\t\t\t\tstruct enic_mbox_hdr *hdr = buf-\u003eos_buf;\ndrivers/net/ethernet/cisco/enic/enic_admin.c-232-\n--\ndrivers/net/ethernet/cisco/enic/enic_admin.c-237-\ndrivers/net/ethernet/cisco/enic/enic_admin.c:238:\t\t\tenic_admin_msg_enqueue(enic, buf-\u003eos_buf,\ndrivers/net/ethernet/cisco/enic/enic_admin.c-239-\t\t\t\t\t bytes_written);\n--\ndrivers/net/ethernet/cisco/enic/enic_admin.c-242-next_desc:\ndrivers/net/ethernet/cisco/enic/enic_admin.c:243:\t\tenic_admin_rq_buf_clean(rq, rq-\u003eto_clean);\ndrivers/net/ethernet/cisco/enic/enic_admin.c-244-\t\trq-\u003eto_clean = rq-\u003eto_clean-\u003enext;\n--\ndrivers/net/ethernet/cisco/enic/enic_admin.c-251-\ndrivers/net/ethernet/cisco/enic/enic_admin.c:252:\tif (enic_admin_rq_fill(enic, GFP_KERNEL)) {\ndrivers/net/ethernet/cisco/enic/enic_admin.c-253-\t\t/* Some RX buffers could not be reposted (transient memory\n--\ndrivers/net/ethernet/cisco/enic/enic_admin.c-259-\t\tif (net_ratelimit())\ndrivers/net/ethernet/cisco/enic/enic_admin.c:260:\t\t\tnetdev_warn(enic-\u003enetdev,\ndrivers/net/ethernet/cisco/enic/enic_admin.c-261-\t\t\t\t \"admin RQ refill failed; scheduling retry\\n\");\ndrivers/net/ethernet/cisco/enic/enic_admin.c:262:\t\tschedule_delayed_work(\u0026enic-\u003eadmin_poll_work,\ndrivers/net/ethernet/cisco/enic/enic_admin.c-263-\t\t\t\t msecs_to_jiffies(ENIC_ADMIN_RQ_REFILL_RETRY_MS));\n--\ndrivers/net/ethernet/cisco/enic/enic_admin.c-268-\ndrivers/net/ethernet/cisco/enic/enic_admin.c:269:static irqreturn_t enic_admin_isr_msix(int irq, void *data)\ndrivers/net/ethernet/cisco/enic/enic_admin.c-270-{\ndrivers/net/ethernet/cisco/enic/enic_admin.c:271:\tstruct enic *enic = data;\ndrivers/net/ethernet/cisco/enic/enic_admin.c-272-\ndrivers/net/ethernet/cisco/enic/enic_admin.c:273:\tschedule_delayed_work(\u0026enic-\u003eadmin_poll_work, 0);\ndrivers/net/ethernet/cisco/enic/enic_admin.c-274-\n--\ndrivers/net/ethernet/cisco/enic/enic_admin.c-277-\ndrivers/net/ethernet/cisco/enic/enic_admin.c:278:static void enic_admin_msg_work_handler(struct work_struct *work)\ndrivers/net/ethernet/cisco/enic/enic_admin.c-279-{\ndrivers/net/ethernet/cisco/enic/enic_admin.c:280:\tstruct enic *enic = container_of(work, struct enic, admin_msg_work);\ndrivers/net/ethernet/cisco/enic/enic_admin.c:281:\tstruct enic_admin_msg *msg, *tmp;\ndrivers/net/ethernet/cisco/enic/enic_admin.c-282-\tLIST_HEAD(local_list);\ndrivers/net/ethernet/cisco/enic/enic_admin.c-283-\ndrivers/net/ethernet/cisco/enic/enic_admin.c:284:\tspin_lock_bh(\u0026enic-\u003eadmin_msg_lock);\ndrivers/net/ethernet/cisco/enic/enic_admin.c:285:\tlist_splice_init(\u0026enic-\u003eadmin_msg_list, \u0026local_list);\ndrivers/net/ethernet/cisco/enic/enic_admin.c:286:\tenic-\u003eadmin_msg_count = 0;\ndrivers/net/ethernet/cisco/enic/enic_admin.c:287:\tspin_unlock_bh(\u0026enic-\u003eadmin_msg_lock);\ndrivers/net/ethernet/cisco/enic/enic_admin.c-288-\ndrivers/net/ethernet/cisco/enic/enic_admin.c-289-\tlist_for_each_entry_safe(msg, tmp, \u0026local_list, list) {\ndrivers/net/ethernet/cisco/enic/enic_admin.c:290:\t\tif (enic-\u003eadmin_rq_handler)\ndrivers/net/ethernet/cisco/enic/enic_admin.c:291:\t\t\tenic-\u003eadmin_rq_handler(enic, msg-\u003edata, msg-\u003elen);\ndrivers/net/ethernet/cisco/enic/enic_admin.c-292-\t\tlist_del(\u0026msg-\u003elist);\n--\ndrivers/net/ethernet/cisco/enic/enic_admin.c-296-\ndrivers/net/ethernet/cisco/enic/enic_admin.c:297:static void enic_admin_poll_work_handler(struct work_struct *work)\ndrivers/net/ethernet/cisco/enic/enic_admin.c-298-{\ndrivers/net/ethernet/cisco/enic/enic_admin.c:299:\tstruct enic *enic = container_of(to_delayed_work(work), struct enic,\ndrivers/net/ethernet/cisco/enic/enic_admin.c-300-\t\t\t\t\t admin_poll_work);\n--\ndrivers/net/ethernet/cisco/enic/enic_admin.c-305-\t * exactly what the hardware reported for this interrupt. Credits that\ndrivers/net/ethernet/cisco/enic/enic_admin.c:306:\t * accrue while enic_admin_rq_cq_service() runs are left for the next\ndrivers/net/ethernet/cisco/enic/enic_admin.c-307-\t * interrupt, which is harmless on this low-rate control path.\ndrivers/net/ethernet/cisco/enic/enic_admin.c-308-\t */\ndrivers/net/ethernet/cisco/enic/enic_admin.c:309:\tcredits = vnic_intr_credits(\u0026enic-\u003eadmin_intr);\ndrivers/net/ethernet/cisco/enic/enic_admin.c-310-\ndrivers/net/ethernet/cisco/enic/enic_admin.c:311:\trq_work = enic_admin_rq_cq_service(enic);\ndrivers/net/ethernet/cisco/enic/enic_admin.c-312-\ndrivers/net/ethernet/cisco/enic/enic_admin.c-313-\tif (rq_work \u003e 0)\ndrivers/net/ethernet/cisco/enic/enic_admin.c:314:\t\tschedule_work(\u0026enic-\u003eadmin_msg_work);\ndrivers/net/ethernet/cisco/enic/enic_admin.c-315-\n--\ndrivers/net/ethernet/cisco/enic/enic_admin.c-322-\t */\ndrivers/net/ethernet/cisco/enic/enic_admin.c:323:\tvnic_intr_return_credits(\u0026enic-\u003eadmin_intr,\ndrivers/net/ethernet/cisco/enic/enic_admin.c-324-\t\t\t\t credits,\n--\ndrivers/net/ethernet/cisco/enic/enic_admin.c-327-\ndrivers/net/ethernet/cisco/enic/enic_admin.c:328:static int enic_admin_setup_intr(struct enic *enic)\ndrivers/net/ethernet/cisco/enic/enic_admin.c-329-{\ndrivers/net/ethernet/cisco/enic/enic_admin.c:330:\tunsigned int intr_index = enic-\u003eintr_count;\ndrivers/net/ethernet/cisco/enic/enic_admin.c-331-\tint err;\ndrivers/net/ethernet/cisco/enic/enic_admin.c-332-\ndrivers/net/ethernet/cisco/enic/enic_admin.c:333:\tif (vnic_dev_get_intr_mode(enic-\u003evdev) != VNIC_DEV_INTR_MODE_MSIX ||\ndrivers/net/ethernet/cisco/enic/enic_admin.c:334:\t intr_index \u003e= enic-\u003eintr_avail)\ndrivers/net/ethernet/cisco/enic/enic_admin.c-335-\t\treturn -ENODEV;\n--\ndrivers/net/ethernet/cisco/enic/enic_admin.c-342-\t */\ndrivers/net/ethernet/cisco/enic/enic_admin.c:343:\terr = vnic_intr_alloc(enic-\u003evdev, \u0026enic-\u003eadmin_intr, intr_index);\ndrivers/net/ethernet/cisco/enic/enic_admin.c-344-\tif (err) {\ndrivers/net/ethernet/cisco/enic/enic_admin.c:345:\t\tnetdev_warn(enic-\u003enetdev,\ndrivers/net/ethernet/cisco/enic/enic_admin.c-346-\t\t\t \"Failed to alloc admin intr at index %u: %d\\n\",\n--\ndrivers/net/ethernet/cisco/enic/enic_admin.c-350-\ndrivers/net/ethernet/cisco/enic/enic_admin.c:351:\tenic-\u003eadmin_intr_index = intr_index;\ndrivers/net/ethernet/cisco/enic/enic_admin.c-352-\n--\ndrivers/net/ethernet/cisco/enic/enic_admin.c-354-\t * spurious completion cannot run the poll handler against the\ndrivers/net/ethernet/cisco/enic/enic_admin.c:355:\t * not-yet-initialised admin rings. enic_admin_channel_open() unmasks\ndrivers/net/ethernet/cisco/enic/enic_admin.c-356-\t * it only after the rings are initialised and filled.\ndrivers/net/ethernet/cisco/enic/enic_admin.c-357-\t */\ndrivers/net/ethernet/cisco/enic/enic_admin.c:358:\tvnic_intr_mask(\u0026enic-\u003eadmin_intr);\ndrivers/net/ethernet/cisco/enic/enic_admin.c-359-\n--\ndrivers/net/ethernet/cisco/enic/enic_admin.c-364-\t */\ndrivers/net/ethernet/cisco/enic/enic_admin.c:365:\tsnprintf(enic-\u003emsix[intr_index].devname,\ndrivers/net/ethernet/cisco/enic/enic_admin.c:366:\t\t sizeof(enic-\u003emsix[intr_index].devname),\ndrivers/net/ethernet/cisco/enic/enic_admin.c:367:\t\t \"%s-admin\", pci_name(enic-\u003epdev));\ndrivers/net/ethernet/cisco/enic/enic_admin.c:368:\tenic-\u003emsix[intr_index].isr = enic_admin_isr_msix;\ndrivers/net/ethernet/cisco/enic/enic_admin.c:369:\tenic-\u003emsix[intr_index].devid = enic;\ndrivers/net/ethernet/cisco/enic/enic_admin.c-370-\ndrivers/net/ethernet/cisco/enic/enic_admin.c:371:\terr = request_irq(enic-\u003emsix_entry[intr_index].vector,\ndrivers/net/ethernet/cisco/enic/enic_admin.c:372:\t\t\t enic-\u003emsix[intr_index].isr, 0,\ndrivers/net/ethernet/cisco/enic/enic_admin.c:373:\t\t\t enic-\u003emsix[intr_index].devname,\ndrivers/net/ethernet/cisco/enic/enic_admin.c:374:\t\t\t enic-\u003emsix[intr_index].devid);\ndrivers/net/ethernet/cisco/enic/enic_admin.c-375-\tif (err) {\ndrivers/net/ethernet/cisco/enic/enic_admin.c:376:\t\tnetdev_warn(enic-\u003enetdev,\ndrivers/net/ethernet/cisco/enic/enic_admin.c-377-\t\t\t \"Failed to request admin MSI-X irq: %d\\n\", err);\ndrivers/net/ethernet/cisco/enic/enic_admin.c:378:\t\tvnic_intr_free(\u0026enic-\u003eadmin_intr);\ndrivers/net/ethernet/cisco/enic/enic_admin.c-379-\t\treturn err;\n--\ndrivers/net/ethernet/cisco/enic/enic_admin.c-381-\ndrivers/net/ethernet/cisco/enic/enic_admin.c:382:\tenic-\u003emsix[intr_index].requested = 1;\ndrivers/net/ethernet/cisco/enic/enic_admin.c-383-\ndrivers/net/ethernet/cisco/enic/enic_admin.c:384:\tnetdev_dbg(enic-\u003enetdev,\ndrivers/net/ethernet/cisco/enic/enic_admin.c-385-\t\t \"admin channel using MSI-X interrupt (index %u)\\n\",\n--\ndrivers/net/ethernet/cisco/enic/enic_admin.c-390-\ndrivers/net/ethernet/cisco/enic/enic_admin.c:391:static void enic_admin_teardown_intr(struct enic *enic)\ndrivers/net/ethernet/cisco/enic/enic_admin.c-392-{\ndrivers/net/ethernet/cisco/enic/enic_admin.c:393:\tunsigned int intr_index = enic-\u003eadmin_intr_index;\ndrivers/net/ethernet/cisco/enic/enic_admin.c-394-\ndrivers/net/ethernet/cisco/enic/enic_admin.c:395:\tfree_irq(enic-\u003emsix_entry[intr_index].vector,\ndrivers/net/ethernet/cisco/enic/enic_admin.c:396:\t\t enic-\u003emsix[intr_index].devid);\ndrivers/net/ethernet/cisco/enic/enic_admin.c:397:\tcancel_delayed_work_sync(\u0026enic-\u003eadmin_poll_work);\ndrivers/net/ethernet/cisco/enic/enic_admin.c:398:\tenic-\u003emsix[intr_index].requested = 0;\ndrivers/net/ethernet/cisco/enic/enic_admin.c-399-}\ndrivers/net/ethernet/cisco/enic/enic_admin.c-400-\ndrivers/net/ethernet/cisco/enic/enic_admin.c:401:static int enic_admin_qp_type_set(struct enic *enic, u32 enable)\ndrivers/net/ethernet/cisco/enic/enic_admin.c-402-{\n--\ndrivers/net/ethernet/cisco/enic/enic_admin.c-406-\ndrivers/net/ethernet/cisco/enic/enic_admin.c:407:\tspin_lock_bh(\u0026enic-\u003edevcmd_lock);\ndrivers/net/ethernet/cisco/enic/enic_admin.c:408:\terr = vnic_dev_cmd(enic-\u003evdev, CMD_QP_TYPE_SET, \u0026a0, \u0026a1, wait);\ndrivers/net/ethernet/cisco/enic/enic_admin.c:409:\tspin_unlock_bh(\u0026enic-\u003edevcmd_lock);\ndrivers/net/ethernet/cisco/enic/enic_admin.c-410-\n--\ndrivers/net/ethernet/cisco/enic/enic_admin.c-413-\ndrivers/net/ethernet/cisco/enic/enic_admin.c:414:static int enic_admin_alloc_resources(struct enic *enic)\ndrivers/net/ethernet/cisco/enic/enic_admin.c-415-{\n--\ndrivers/net/ethernet/cisco/enic/enic_admin.c-417-\ndrivers/net/ethernet/cisco/enic/enic_admin.c:418:\terr = vnic_wq_alloc_with_type(enic-\u003evdev, \u0026enic-\u003eadmin_wq, 0,\ndrivers/net/ethernet/cisco/enic/enic_admin.c-419-\t\t\t\t ENIC_ADMIN_DESC_COUNT,\n--\ndrivers/net/ethernet/cisco/enic/enic_admin.c-424-\ndrivers/net/ethernet/cisco/enic/enic_admin.c:425:\terr = vnic_rq_alloc_with_type(enic-\u003evdev, \u0026enic-\u003eadmin_rq, 0,\ndrivers/net/ethernet/cisco/enic/enic_admin.c-426-\t\t\t\t ENIC_ADMIN_DESC_COUNT,\n--\ndrivers/net/ethernet/cisco/enic/enic_admin.c-436-\t */\ndrivers/net/ethernet/cisco/enic/enic_admin.c:437:\terr = vnic_cq_alloc_with_type(enic-\u003evdev, \u0026enic-\u003eadmin_cq[0], 0,\ndrivers/net/ethernet/cisco/enic/enic_admin.c-438-\t\t\t\t ENIC_ADMIN_DESC_COUNT,\n--\ndrivers/net/ethernet/cisco/enic/enic_admin.c-444-\t/* admin_cq[1] is the RQ completion queue. Its descriptor size\ndrivers/net/ethernet/cisco/enic/enic_admin.c:445:\t * must match what firmware writes. enic_ext_cq() called earlier\ndrivers/net/ethernet/cisco/enic/enic_admin.c-446-\t * in probe issues CMD_CQ_ENTRY_SIZE_SET for VNIC_RQ_ALL,\ndrivers/net/ethernet/cisco/enic/enic_admin.c:447:\t * programming firmware to write CQ entries of (16 \u003c\u003c enic-\u003eext_cq)\ndrivers/net/ethernet/cisco/enic/enic_admin.c-448-\t * bytes for every RQ CQ on the vNIC, including the admin RQ CQ.\n\n"
} |
| 9/2 | 2026/08/12 13:56 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 45765 output: 40 thoughts: 527 Thoughts: **WorthFuzzing=False: Cisco VIC Ethernet NIC Driver** Okay, let's break this down. I'm looking at a patch that seems to touch the `enic` driver, which is for Cisco VIC Ethernet NICs. The immediate question is, can this code be reached within a QEMU environment? The prompt explicitly states to return `WorthFuzzing=false` if the patch *only* contains code in vendor-specific PCIe switch, SmartNIC, or GPU drivers that need physical hardware, and aren't emulated in standard QEMU. That's the key here. First, I need to confirm if QEMU even *emulates* a Cisco VIC. Quick check... nope, QEMU doesn't emulate the Cisco VIC. I know QEMU supports the usual suspects like e1000, virtio-net, etc., but `enic` isn't on that list. I would normally check the `hw/net/` directory of QEMU source, or simply search the source for "enic" if it was available here. But the point is moot, I know it doesn't. So, since it's a vendor-specific NIC, and it requires physical hardware, and *this* patch only changes the `drivers/net/ethernet/cisco/enic/` directory, there is no way to exercise this code within standard virtualization environments like GCE or QEMU. It's essentially unreachable in a syzkaller VM. Just to be absolutely sure, I'll double-check its hardware dependency. The driver uses `pci_register_driver` and binds to Cisco's specific PCI vendor/device IDs (0x1137). There isn't any software emulation of Cisco VIC in the upstream kernel, something like `mac80211_hwsim` or `netdevsim`. Therefore, this code is, practically, impossible to reach in the standard syzkaller VM setup. This lines up perfectly with the prompt's criteria: `enic` is a vendor-specific NIC driver (Cisco VIC), and it relies on physical PCIe hardware. We can confidently say this patch is not worth fuzzing. The code in the patch is not reachable. |
| 10/2 | 2026/08/12 13:56 | tool | grepper |
0mArgs:{
"Expression": "PCI_DEVICE_ID_CISCO",
"PathPrefix": "drivers/net/ethernet/cisco/enic/"
}Results: {
"Output": "drivers/net/ethernet/cisco/enic/enic_main.c-67-\ndrivers/net/ethernet/cisco/enic/enic_main.c:68:#define PCI_DEVICE_ID_CISCO_VIC_ENET 0x0043 /* ethernet vnic */\ndrivers/net/ethernet/cisco/enic/enic_main.c:69:#define PCI_DEVICE_ID_CISCO_VIC_ENET_DYN 0x0044 /* enet dynamic vnic */\ndrivers/net/ethernet/cisco/enic/enic_main.c:70:#define PCI_DEVICE_ID_CISCO_VIC_ENET_VF 0x0071 /* enet SRIOV VF */\ndrivers/net/ethernet/cisco/enic/enic_main.c:71:#define PCI_DEVICE_ID_CISCO_VIC_ENET_VF_V2 0x02b7 /* enet SRIOV V2 VF */\ndrivers/net/ethernet/cisco/enic/enic_main.c:72:#define PCI_DEVICE_ID_CISCO_VIC_ENET_VF_USNIC 0x00cf /* enet USNIC VF */\ndrivers/net/ethernet/cisco/enic/enic_main.c-73-\n--\ndrivers/net/ethernet/cisco/enic/enic_main.c=75=static const struct pci_device_id enic_id_table[] = {\ndrivers/net/ethernet/cisco/enic/enic_main.c:76:\t{ PCI_VDEVICE(CISCO, PCI_DEVICE_ID_CISCO_VIC_ENET) },\ndrivers/net/ethernet/cisco/enic/enic_main.c:77:\t{ PCI_VDEVICE(CISCO, PCI_DEVICE_ID_CISCO_VIC_ENET_DYN) },\ndrivers/net/ethernet/cisco/enic/enic_main.c:78:\t{ PCI_VDEVICE(CISCO, PCI_DEVICE_ID_CISCO_VIC_ENET_VF) },\ndrivers/net/ethernet/cisco/enic/enic_main.c:79:\t{ PCI_VDEVICE(CISCO, PCI_DEVICE_ID_CISCO_VIC_ENET_VF_V2) },\ndrivers/net/ethernet/cisco/enic/enic_main.c-80-\t{ 0, }\t/* end of table */\n--\ndrivers/net/ethernet/cisco/enic/enic_main.c=303=int enic_is_dynamic(struct enic *enic)\ndrivers/net/ethernet/cisco/enic/enic_main.c-304-{\ndrivers/net/ethernet/cisco/enic/enic_main.c:305:\treturn enic-\u003epdev-\u003edevice == PCI_DEVICE_ID_CISCO_VIC_ENET_DYN;\ndrivers/net/ethernet/cisco/enic/enic_main.c-306-}\n--\ndrivers/net/ethernet/cisco/enic/enic_main.c=313=static int enic_is_sriov_vf(struct enic *enic)\ndrivers/net/ethernet/cisco/enic/enic_main.c-314-{\ndrivers/net/ethernet/cisco/enic/enic_main.c:315:\treturn enic-\u003epdev-\u003edevice == PCI_DEVICE_ID_CISCO_VIC_ENET_VF ||\ndrivers/net/ethernet/cisco/enic/enic_main.c:316:\t enic-\u003epdev-\u003edevice == PCI_DEVICE_ID_CISCO_VIC_ENET_VF_V2;\ndrivers/net/ethernet/cisco/enic/enic_main.c-317-}\n--\ndrivers/net/ethernet/cisco/enic/enic_main.c=319=int enic_is_sriov_vf_v2(struct enic *enic)\ndrivers/net/ethernet/cisco/enic/enic_main.c-320-{\ndrivers/net/ethernet/cisco/enic/enic_main.c:321:\treturn enic-\u003epdev-\u003edevice == PCI_DEVICE_ID_CISCO_VIC_ENET_VF_V2;\ndrivers/net/ethernet/cisco/enic/enic_main.c-322-}\n--\ndrivers/net/ethernet/cisco/enic/enic_main.c=2798=static void enic_sriov_detect_vf_type(struct enic *enic)\n--\ndrivers/net/ethernet/cisco/enic/enic_main.c-2817-\tswitch (vf_dev_id) {\ndrivers/net/ethernet/cisco/enic/enic_main.c:2818:\tcase PCI_DEVICE_ID_CISCO_VIC_ENET_VF:\ndrivers/net/ethernet/cisco/enic/enic_main.c-2819-\t\tenic-\u003evf_type = ENIC_VF_TYPE_V1;\ndrivers/net/ethernet/cisco/enic/enic_main.c-2820-\t\tbreak;\ndrivers/net/ethernet/cisco/enic/enic_main.c:2821:\tcase PCI_DEVICE_ID_CISCO_VIC_ENET_VF_USNIC:\ndrivers/net/ethernet/cisco/enic/enic_main.c-2822-\t\tenic-\u003evf_type = ENIC_VF_TYPE_USNIC;\ndrivers/net/ethernet/cisco/enic/enic_main.c-2823-\t\tbreak;\ndrivers/net/ethernet/cisco/enic/enic_main.c:2824:\tcase PCI_DEVICE_ID_CISCO_VIC_ENET_VF_V2:\ndrivers/net/ethernet/cisco/enic/enic_main.c-2825-\t\tenic-\u003evf_type = ENIC_VF_TYPE_V2;\n"
} |
| 11/2 | 2026/08/12 13:56 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 48271 output: 72 thoughts: 110 Thoughts: **My Assessment of the Fuzzing Applicability** Okay, so I've examined the code and, yes, it's clear: this is just a low-level hardware driver for the Cisco VIC. My analysis confirms this is the `enic` driver, specifically. The issue is straightforward: this driver relies on the physical presence of a PCIe device that isn't emulated by standard QEMU environments. This means running a fuzzing campaign within a typical QEMU VM won't even *touch* the code in question. It's simply unreachable in that context. Given this hardware dependency, there's no way the changes introduced by the patch could be effectively fuzzed using this setup. Therefore, I need to call `set-results` and declare `WorthFuzzing=false`. It's a waste of resources to proceed with a fuzzing campaign under these conditions. |
| 12/2 | 2026/08/12 13:57 | tool | set-results |
0mArgs:{
"EnableConfigs": [],
"FocusSymbols": [],
"Reasoning": "The patch only modifies the `enic` driver (Cisco VIC Ethernet NIC), which requires physical PCIe hardware not emulated in standard QEMU environments. Thus, the code is unreachable during fuzzing.",
"WorthFuzzing": false
}Results: {
"EnableConfigs": null,
"FocusSymbols": [],
"Reasoning": "The patch only modifies the `enic` driver (Cisco VIC Ethernet NIC), which requires physical PCIe hardware not emulated in standard QEMU environments. Thus, the code is unreachable during fuzzing.",
"WorthFuzzing": false
} |
| 13/1 | 2026/08/12 13:57 | action | If |
0mArgs:{
"WorthFuzzing": false
}Results: null |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|