mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-31 08:15:07 -04:00
enic: add MBOX core send and receive for admin channel
Implement the mailbox protocol engine used for PF-VF communication over the admin channel. The send path (enic_mbox_send_msg) builds a message with a common header, DMA-maps it, posts a single WQ descriptor with the destination vnic ID encoded in the VLAN tag field, and polls the WQ CQ for completion. The total message length is computed as a size_t, and the payload is bounded before the send lock is taken: a payload larger than the admin buffer minus the header is rejected with -EINVAL. This keeps the length sum from wrapping and stops the on-the-wire u16 length from overflowing or the DMA buffer from being overrun. MBOX sends are gated by enic->mbox_send_disabled: enic_mbox_send_msg() returns early while it is set. It is set at the very start of both enic_admin_channel_open() and enic_admin_channel_close(), and is cleared in enic_admin_channel_open() only once the admin WQ/RQ/CQ and interrupt are fully allocated, programmed and enabled. Keeping it set for the whole open sequence means an early failure that returns before the channel is ready (as well as a not-yet-ready or torn-down channel) leaves sends disabled, so a concurrent sender can never race an MBOX send against a half-open or freed admin_wq. The receive path (enic_mbox_recv_handler) is installed as the admin RQ callback and validates incoming message headers. PF/VF-specific dispatch will be added in subsequent commits. Signed-off-by: Satish Kharat <satishkh@cisco.com> Link: https://patch.msgid.link/20260812-enic-sriov-v2-admin-channel-v2-v13-6-b3809e448aba@cisco.com Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
committed by
Jakub Kicinski
parent
1584793311
commit
1f0c856b59
@@ -4,5 +4,5 @@ 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_admin.o
|
||||
enic_admin.o enic_mbox.o
|
||||
|
||||
|
||||
@@ -297,6 +297,8 @@ struct enic {
|
||||
* 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];
|
||||
@@ -309,6 +311,10 @@ struct enic {
|
||||
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;
|
||||
};
|
||||
|
||||
static inline struct net_device *vnic_get_netdev(struct vnic_dev *vdev)
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#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.
|
||||
@@ -217,7 +218,26 @@ unsigned int enic_admin_rq_cq_service(struct enic *enic)
|
||||
goto next_desc;
|
||||
}
|
||||
|
||||
enic_admin_msg_enqueue(enic, buf->os_buf, bytes_written);
|
||||
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);
|
||||
@@ -490,8 +510,9 @@ static void enic_admin_init_resources(struct enic *enic)
|
||||
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); /* coalescing_timer, coalescing_type, mask_on_assertion */
|
||||
0, 0, 1);
|
||||
}
|
||||
|
||||
static void enic_admin_msg_drain(struct enic *enic)
|
||||
@@ -514,6 +535,13 @@ int enic_admin_channel_open(struct enic *enic)
|
||||
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,
|
||||
@@ -556,6 +584,14 @@ int enic_admin_channel_open(struct enic *enic)
|
||||
|
||||
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,
|
||||
@@ -597,6 +633,8 @@ void enic_admin_channel_close(struct enic *enic)
|
||||
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);
|
||||
|
||||
177
drivers/net/ethernet/cisco/enic/enic_mbox.c
Normal file
177
drivers/net/ethernet/cisco/enic/enic_mbox.c
Normal file
@@ -0,0 +1,177 @@
|
||||
// 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 "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 void enic_mbox_recv_handler(struct enic *enic, void *buf,
|
||||
unsigned int len)
|
||||
{
|
||||
struct enic_mbox_hdr *hdr = buf;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
netdev_dbg(enic->netdev,
|
||||
"MBOX recv: type %u from vnic %u len %u\n",
|
||||
hdr->msg_type, le16_to_cpu(hdr->src_vnic_id),
|
||||
le16_to_cpu(hdr->msg_len));
|
||||
}
|
||||
|
||||
void enic_mbox_init(struct enic *enic)
|
||||
{
|
||||
enic->mbox_msg_num = 0;
|
||||
mutex_init(&enic->mbox_lock);
|
||||
enic->admin_rq_handler = enic_mbox_recv_handler;
|
||||
}
|
||||
@@ -80,4 +80,12 @@ 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);
|
||||
|
||||
#endif /* _ENIC_MBOX_H_ */
|
||||
|
||||
Reference in New Issue
Block a user