The V2 SR-IOV design uses a dedicated admin channel (WQ/RQ/CQ/INTR on separate BAR resources) for PF-VF mailbox communication rather than firmware-proxied devcmds. Introduce enic_admin_channel_open() and enic_admin_channel_close(). Open allocates and initialises the admin WQ, RQ, and two CQs (one per direction), then issues CMD_QP_TYPE_SET to tell firmware the queues are admin-type. Close reverses the sequence. enic_admin_wq_buf_clean() unmaps and frees any WQ buffers still held at close time, fixing a DMA mapping leak when a send times out. Add CMD_QP_TYPE_SET (97), QP_TYPE_ADMIN/DATA, and QP_ENABLE/QP_DISABLE defines to vnic_devcmd.h. Add VNIC_CQ_* named constants to vnic_cq.h so CQ initialisation parameters are self-documenting from their first introduction. Signed-off-by: Satish Kharat --- drivers/net/ethernet/cisco/enic/Makefile | 3 +- drivers/net/ethernet/cisco/enic/enic_admin.c | 216 ++++++++++++++++++++++++++ drivers/net/ethernet/cisco/enic/enic_admin.h | 15 ++ drivers/net/ethernet/cisco/enic/vnic_cq.h | 9 ++ drivers/net/ethernet/cisco/enic/vnic_devcmd.h | 11 ++ 5 files changed, 253 insertions(+), 1 deletion(-) diff --git a/drivers/net/ethernet/cisco/enic/Makefile b/drivers/net/ethernet/cisco/enic/Makefile index a96b8332e6e2..7ae72fefc99a 100644 --- a/drivers/net/ethernet/cisco/enic/Makefile +++ b/drivers/net/ethernet/cisco/enic/Makefile @@ -3,5 +3,6 @@ obj-$(CONFIG_ENIC) := enic.o enic-y := enic_main.o vnic_cq.o vnic_intr.o vnic_wq.o \ enic_res.o enic_dev.o enic_pp.o vnic_dev.o vnic_rq.o vnic_vic.o \ - enic_ethtool.o enic_api.o enic_clsf.o enic_rq.o enic_wq.o + enic_ethtool.o enic_api.o enic_clsf.o enic_rq.o enic_wq.o \ + enic_admin.o diff --git a/drivers/net/ethernet/cisco/enic/enic_admin.c b/drivers/net/ethernet/cisco/enic/enic_admin.c new file mode 100644 index 000000000000..aa21868a9209 --- /dev/null +++ b/drivers/net/ethernet/cisco/enic/enic_admin.c @@ -0,0 +1,216 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright 2025 Cisco Systems, Inc. All rights reserved. + +#include +#include + +#include "vnic_dev.h" +#include "vnic_wq.h" +#include "vnic_rq.h" +#include "vnic_cq.h" +#include "vnic_intr.h" +#include "vnic_resource.h" +#include "vnic_devcmd.h" +#include "enic.h" +#include "enic_admin.h" +#include "cq_desc.h" +#include "wq_enet_desc.h" +#include "rq_enet_desc.h" + +/* Clean up any admin WQ buffers still held by hardware at close time. + * Normally buffers are freed inline after send completion, but a timed-out + * send intentionally leaves the buffer live until the queue is stopped. + */ +static void enic_admin_wq_buf_clean(struct vnic_wq *wq, + struct vnic_wq_buf *buf) +{ + struct enic *enic = vnic_dev_priv(wq->vdev); + + if (buf->os_buf) { + dma_unmap_single(&enic->pdev->dev, buf->dma_addr, + buf->len, DMA_TO_DEVICE); + kfree(buf->os_buf); + buf->os_buf = NULL; + } +} + +/* No-op: admin RQ buffer teardown is handled in enic_admin_channel_close */ +static void enic_admin_rq_buf_clean(struct vnic_rq *rq, + struct vnic_rq_buf *buf) +{ +} + +static int enic_admin_qp_type_set(struct enic *enic, u32 enable) +{ + u64 a0 = QP_TYPE_ADMIN, a1 = enable; + int wait = 1000; + int err; + + spin_lock_bh(&enic->devcmd_lock); + err = vnic_dev_cmd(enic->vdev, CMD_QP_TYPE_SET, &a0, &a1, wait); + spin_unlock_bh(&enic->devcmd_lock); + + return err; +} + +static int enic_admin_alloc_resources(struct enic *enic) +{ + int err; + + err = vnic_wq_alloc_with_type(enic->vdev, &enic->admin_wq, 0, + ENIC_ADMIN_DESC_COUNT, + sizeof(struct wq_enet_desc), + RES_TYPE_ADMIN_WQ); + if (err) + return err; + + err = vnic_rq_alloc_with_type(enic->vdev, &enic->admin_rq, 0, + ENIC_ADMIN_DESC_COUNT, + sizeof(struct rq_enet_desc), + RES_TYPE_ADMIN_RQ); + if (err) + goto free_wq; + + /* admin_cq[0] is the WQ completion queue. WQ CQEs are always + * 16 bytes wide; firmware always writes 16-byte CQEs for WQ + * completions on every WQ, including the admin channel WQ. + * Use sizeof(struct cq_desc) accordingly. + */ + err = vnic_cq_alloc_with_type(enic->vdev, &enic->admin_cq[0], 0, + ENIC_ADMIN_DESC_COUNT, + sizeof(struct cq_desc), + RES_TYPE_ADMIN_CQ); + if (err) + goto free_rq; + + /* admin_cq[1] is the RQ completion queue. Its descriptor size + * must match what firmware writes. enic_ext_cq() called earlier + * in probe issues CMD_CQ_ENTRY_SIZE_SET for VNIC_RQ_ALL, + * programming firmware to write CQ entries of (16 << enic->ext_cq) + * bytes for every RQ CQ on the vNIC, including the admin RQ CQ. + * Allocating with the same size keeps the host poller and + * firmware in lockstep: + * + * - The color/valid bit lives at byte (desc_size - 1) of every + * cq_enet_rq_desc[_32|_64] variant, so enic_admin_cq_color() + * reads it from the correct offset. + * - Only the first 15 bytes of the descriptor (vlan, + * bytes_written_flags, ...) are accessed by the admin path; + * these fields are identical across all three variants (see + * comment in enic_rq.c above cq_enet_rq_desc_dec()). + */ + err = vnic_cq_alloc_with_type(enic->vdev, &enic->admin_cq[1], 1, + ENIC_ADMIN_DESC_COUNT, + 16 << enic->ext_cq, + RES_TYPE_ADMIN_CQ); + if (err) + goto free_cq0; + + return 0; + +free_cq0: + vnic_cq_free(&enic->admin_cq[0]); +free_rq: + vnic_rq_free(&enic->admin_rq); +free_wq: + vnic_wq_free(&enic->admin_wq); + return err; +} + +static void enic_admin_free_resources(struct enic *enic) +{ + vnic_cq_free(&enic->admin_cq[1]); + vnic_cq_free(&enic->admin_cq[0]); + vnic_rq_free(&enic->admin_rq); + vnic_wq_free(&enic->admin_wq); +} + +static void enic_admin_init_resources(struct enic *enic) +{ + vnic_wq_init(&enic->admin_wq, + 0, 0, 0); /* cq_index, err_intr_enable, err_intr_offset */ + vnic_rq_init(&enic->admin_rq, + 1, 0, 0); /* cq_index, err_intr_enable, err_intr_offset */ + vnic_cq_init(&enic->admin_cq[0], + VNIC_CQ_FC_DISABLE, + VNIC_CQ_COLOR_ENABLE, + 0, 0, 1, /* cq_head, cq_tail, cq_tail_color */ + VNIC_CQ_INTR_DISABLE, + VNIC_CQ_ENTRY_ENABLE, + VNIC_CQ_MSG_DISABLE, + 0, /* interrupt_offset */ + 0 /* cq_message_addr */); + vnic_cq_init(&enic->admin_cq[1], + VNIC_CQ_FC_DISABLE, + VNIC_CQ_COLOR_ENABLE, + 0, 0, 1, /* cq_head, cq_tail, cq_tail_color */ + VNIC_CQ_INTR_DISABLE, + VNIC_CQ_ENTRY_ENABLE, + VNIC_CQ_MSG_DISABLE, + 0, /* interrupt_offset */ + 0 /* cq_message_addr */); +} + +int enic_admin_channel_open(struct enic *enic) +{ + int err; + + if (!enic->has_admin_channel) + return -ENODEV; + + err = enic_admin_alloc_resources(enic); + if (err) { + netdev_err(enic->netdev, + "Failed to alloc admin channel resources: %d\n", + err); + return err; + } + + enic_admin_init_resources(enic); + + vnic_wq_enable(&enic->admin_wq); + vnic_rq_enable(&enic->admin_rq); + + err = enic_admin_qp_type_set(enic, QP_ENABLE); + if (err) { + netdev_err(enic->netdev, + "Failed to set admin QP type: %d\n", err); + goto disable_queues; + } + + return 0; + +disable_queues: + enic_admin_qp_type_set(enic, QP_DISABLE); + if (vnic_wq_disable(&enic->admin_wq)) + netdev_warn(enic->netdev, "Failed to disable admin WQ\n"); + if (vnic_rq_disable(&enic->admin_rq)) + netdev_warn(enic->netdev, "Failed to disable admin RQ\n"); + enic_admin_free_resources(enic); + return err; +} + +void enic_admin_channel_close(struct enic *enic) +{ + int err; + + if (!enic->has_admin_channel) + return; + + enic_admin_qp_type_set(enic, QP_DISABLE); + + err = vnic_wq_disable(&enic->admin_wq); + if (err) + netdev_warn(enic->netdev, + "Failed to disable admin WQ: %d\n", err); + err = vnic_rq_disable(&enic->admin_rq); + if (err) + netdev_warn(enic->netdev, + "Failed to disable admin RQ: %d\n", err); + + vnic_wq_clean(&enic->admin_wq, enic_admin_wq_buf_clean); + vnic_rq_clean(&enic->admin_rq, enic_admin_rq_buf_clean); + vnic_cq_clean(&enic->admin_cq[0]); + vnic_cq_clean(&enic->admin_cq[1]); + enic_admin_free_resources(enic); +} diff --git a/drivers/net/ethernet/cisco/enic/enic_admin.h b/drivers/net/ethernet/cisco/enic/enic_admin.h new file mode 100644 index 000000000000..569aadeb9312 --- /dev/null +++ b/drivers/net/ethernet/cisco/enic/enic_admin.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright 2025 Cisco Systems, Inc. All rights reserved. */ + +#ifndef _ENIC_ADMIN_H_ +#define _ENIC_ADMIN_H_ + +#define ENIC_ADMIN_DESC_COUNT 64 +#define ENIC_ADMIN_BUF_SIZE 2048 + +struct enic; + +int enic_admin_channel_open(struct enic *enic); +void enic_admin_channel_close(struct enic *enic); + +#endif /* _ENIC_ADMIN_H_ */ diff --git a/drivers/net/ethernet/cisco/enic/vnic_cq.h b/drivers/net/ethernet/cisco/enic/vnic_cq.h index d46d4d2ef6bb..35ffa3230713 100644 --- a/drivers/net/ethernet/cisco/enic/vnic_cq.h +++ b/drivers/net/ethernet/cisco/enic/vnic_cq.h @@ -76,6 +76,15 @@ int vnic_cq_alloc(struct vnic_dev *vdev, struct vnic_cq *cq, unsigned int index, int vnic_cq_alloc_with_type(struct vnic_dev *vdev, struct vnic_cq *cq, unsigned int index, unsigned int desc_count, unsigned int desc_size, unsigned int res_type); +#define VNIC_CQ_FC_ENABLE 1 +#define VNIC_CQ_FC_DISABLE 0 +#define VNIC_CQ_COLOR_ENABLE 1 +#define VNIC_CQ_INTR_ENABLE 1 +#define VNIC_CQ_INTR_DISABLE 0 +#define VNIC_CQ_ENTRY_ENABLE 1 +#define VNIC_CQ_MSG_ENABLE 1 +#define VNIC_CQ_MSG_DISABLE 0 + void vnic_cq_init(struct vnic_cq *cq, unsigned int flow_control_enable, unsigned int color_enable, unsigned int cq_head, unsigned int cq_tail, unsigned int cq_tail_color, unsigned int interrupt_enable, diff --git a/drivers/net/ethernet/cisco/enic/vnic_devcmd.h b/drivers/net/ethernet/cisco/enic/vnic_devcmd.h index 3b6efa743dba..90ca06691ebd 100644 --- a/drivers/net/ethernet/cisco/enic/vnic_devcmd.h +++ b/drivers/net/ethernet/cisco/enic/vnic_devcmd.h @@ -455,8 +455,19 @@ enum vnic_devcmd_cmd { */ CMD_CQ_ENTRY_SIZE_SET = _CMDC(_CMD_DIR_WRITE, _CMD_VTYPE_ENET, 90), + /* + * Set queue pair type (admin or data) + * in: (u32) a0 = queue pair type (0 = admin, 1 = data) + * in: (u32) a1 = enable (1) / disable (0) + */ + CMD_QP_TYPE_SET = _CMDC(_CMD_DIR_WRITE, _CMD_VTYPE_ENET, 97), }; +#define QP_TYPE_ADMIN 0 +#define QP_TYPE_DATA 1 +#define QP_ENABLE 1 +#define QP_DISABLE 0 + /* CMD_ENABLE2 flags */ #define CMD_ENABLE2_STANDBY 0x0 #define CMD_ENABLE2_ACTIVE 0x1 -- 2.43.0