mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-31 03:35:32 -04:00
enic: add admin channel open and close for SR-IOV
The V2 SR-IOV design uses a dedicated admin channel (WQ/RQ/CQ resources plus an MSI-X interrupt) 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 <satishkh@cisco.com> Link: https://patch.msgid.link/20260812-enic-sriov-v2-admin-channel-v2-v13-2-b3809e448aba@cisco.com Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
committed by
Jakub Kicinski
parent
a16975e223
commit
3258931d40
@@ -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
|
||||
|
||||
|
||||
@@ -292,6 +292,11 @@ 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;
|
||||
struct vnic_wq admin_wq;
|
||||
struct vnic_rq admin_rq;
|
||||
struct vnic_cq admin_cq[2];
|
||||
|
||||
227
drivers/net/ethernet/cisco/enic/enic_admin.c
Normal file
227
drivers/net/ethernet/cisco/enic/enic_admin.c
Normal file
@@ -0,0 +1,227 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
// Copyright 2025 Cisco Systems, Inc. All rights reserved.
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/netdevice.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 "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;
|
||||
}
|
||||
|
||||
enic->admin_chan_up = true;
|
||||
|
||||
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;
|
||||
|
||||
/* 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;
|
||||
|
||||
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);
|
||||
|
||||
enic->admin_chan_up = false;
|
||||
}
|
||||
15
drivers/net/ethernet/cisco/enic/enic_admin.h
Normal file
15
drivers/net/ethernet/cisco/enic/enic_admin.h
Normal file
@@ -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_ */
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user