diff options
| author | Jakub Kicinski <kuba@kernel.org> | 2026-08-17 17:48:47 -0700 |
|---|---|---|
| committer | Jakub Kicinski <kuba@kernel.org> | 2026-08-17 17:48:47 -0700 |
| commit | 2d380a4fede92db07b930b99f1ec0f85e0f132a3 (patch) | |
| tree | 25a0a54974240889566bd52cd7a9f961c88330a4 | |
| parent | 553989fc8ac1e6a7f79e9aafe0b4b1bba0b9fc12 (diff) | |
| parent | 885f462fe90ebd0c398815763b051ba5fc3fae5f (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/Makefile | 3 | ||||
| -rw-r--r-- | drivers/net/ethernet/cisco/enic/enic.h | 41 | ||||
| -rw-r--r-- | drivers/net/ethernet/cisco/enic/enic_admin.c | 667 | ||||
| -rw-r--r-- | drivers/net/ethernet/cisco/enic/enic_admin.h | 27 | ||||
| -rw-r--r-- | drivers/net/ethernet/cisco/enic/enic_main.c | 431 | ||||
| -rw-r--r-- | drivers/net/ethernet/cisco/enic/enic_mbox.c | 647 | ||||
| -rw-r--r-- | drivers/net/ethernet/cisco/enic/enic_mbox.h | 95 | ||||
| -rw-r--r-- | drivers/net/ethernet/cisco/enic/enic_pp.c | 5 | ||||
| -rw-r--r-- | drivers/net/ethernet/cisco/enic/enic_res.c | 4 | ||||
| -rw-r--r-- | drivers/net/ethernet/cisco/enic/vnic_cq.h | 9 | ||||
| -rw-r--r-- | drivers/net/ethernet/cisco/enic/vnic_devcmd.h | 13 | ||||
| -rw-r--r-- | drivers/net/ethernet/cisco/enic/vnic_enet.h | 4 |
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, |
