aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakub Kicinski <kuba@kernel.org>2026-08-17 17:48:47 -0700
committerJakub Kicinski <kuba@kernel.org>2026-08-17 17:48:47 -0700
commit2d380a4fede92db07b930b99f1ec0f85e0f132a3 (patch)
tree25a0a54974240889566bd52cd7a9f961c88330a4
parent553989fc8ac1e6a7f79e9aafe0b4b1bba0b9fc12 (diff)
parent885f462fe90ebd0c398815763b051ba5fc3fae5f (diff)
Merge branch 'enic-sr-iov-v2-admin-channel-and-mbox-protocol'
Satish Kharat says: ==================== enic: SR-IOV V2 admin channel and MBOX protocol This series adds the admin channel infrastructure and mailbox (MBOX) protocol needed for V2 SR-IOV support in the enic driver. The V2 SR-IOV design uses a direct PF-VF communication channel built on dedicated WQ/RQ/CQ hardware resources and an MSI-X interrupt. This series touches enic_remove(), which is also modified by a fix already applied to net: enic: fix tx_hang_reset use-after-free on device removal https://git.kernel.org/netdev/net/c/ec680ea4ba1b That fix was previously carried at the head of this series (patch 1 in v10-v12); it has been split out and applied to net separately. This net-next series no longer includes it and applies cleanly on current net-next, which already contains that fix (via the net -> net-next merge); the overlapping enic_remove() teardown-ordering change is incorporated in this series. Firmware capability and admin channel infrastructure (patches 1-4): - Probe-time firmware feature check for V2 SR-IOV support - Admin channel open/close, RQ buffer management, CQ service with MSI-X interrupt and workqueue-based polling MBOX protocol and VF enable (patches 5-10): - MBOX message types, core send/receive, PF and VF handlers - V2 SR-IOV enable wiring with admin channel setup - V2 VF probe with admin channel and PF registration Patch 11 completes reset recovery for V2 VFs: the reset paths added earlier in the series re-establish the admin channel only for the PF, which left a VF unregistered and unable to exchange MBOX traffic after a reset taken on the VF. Known follow-ups (not blocking this series; the V2 PF data path is not yet activated and V2 VFs are brought up only in a later series that wires .sriov_configure): - Full request/reply correlation by msg_num requires the MBOX peer to echo the request number; this series retains the existing single-outstanding-request model with reply-type gating. - PF-side registration synchronization, initial link-state retry, and admin control-message throttling will be completed alongside the .sriov_configure enablement. ==================== Link: https://patch.msgid.link/20260812-enic-sriov-v2-admin-channel-v2-v13-0-b3809e448aba@cisco.com Signed-off-by: Jakub Kicinski <kuba@kernel.org>
-rw-r--r--drivers/net/ethernet/cisco/enic/Makefile3
-rw-r--r--drivers/net/ethernet/cisco/enic/enic.h41
-rw-r--r--drivers/net/ethernet/cisco/enic/enic_admin.c667
-rw-r--r--drivers/net/ethernet/cisco/enic/enic_admin.h27
-rw-r--r--drivers/net/ethernet/cisco/enic/enic_main.c431
-rw-r--r--drivers/net/ethernet/cisco/enic/enic_mbox.c647
-rw-r--r--drivers/net/ethernet/cisco/enic/enic_mbox.h95
-rw-r--r--drivers/net/ethernet/cisco/enic/enic_pp.c5
-rw-r--r--drivers/net/ethernet/cisco/enic/enic_res.c4
-rw-r--r--drivers/net/ethernet/cisco/enic/vnic_cq.h9
-rw-r--r--drivers/net/ethernet/cisco/enic/vnic_devcmd.h13
-rw-r--r--drivers/net/ethernet/cisco/enic/vnic_enet.h4
12 files changed, 1924 insertions, 22 deletions
diff --git a/drivers/net/ethernet/cisco/enic/Makefile b/drivers/net/ethernet/cisco/enic/Makefile
index a96b8332e6e2..e38aaf34c148 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 08472420f3a1..4a67947cfb9f 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 000000000000..7188f1b81c04
--- /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 000000000000..62c80220b0ca
--- /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 d98f7e7ccab9..0baef7a120ec 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,