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>
This commit is contained in:
Jakub Kicinski
2026-08-17 17:48:47 -07:00
12 changed files with 1924 additions and 22 deletions

View File

@@ -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

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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_ */

View File

@@ -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);

View File

@@ -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,
&notif, 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;
}

View File

@@ -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_ */

View File

@@ -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;

View File

@@ -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",

View File

@@ -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,

View File

@@ -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,
};

View File

@@ -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