[PATCH net-next 4/6] enic: validate V2 VF configuration replies

From: Satish Kharat

Date: Mon Sep 21 2026 - 16:20:24 EST


Add the established V2 mailbox operations for MAC filters,
administrative-MAC notifications, and packet-filter settings. Keep request
buffers alive until the VF request completes so detailed replies can be
checked entry by entry.

Validate message framing, echoed operations, result counts,
operation-specific idempotent results, and applied packet-filter flags
before publishing a reply. If a reply is malformed or contradictory, or
the PF reports that the VF is no longer registered, the VF can no longer
trust that its state matches the PF. Require a new VF registration before
accepting traffic again.

The protocol's ret_minor field counts non-SKIPPED per-entry result codes,
including the idempotent DUPLICATE and NOT_FOUND outcomes. SKIPPED remains
an operation-specific policy result but is not part of that aggregate
count.

Return stable policy errors to callers while retaining retry semantics for
operations that made no state change.

This patch adds only the VF side of these operations. The in-tree V2 PF
enable path remains dormant because enic_driver does not yet register
.sriov_configure. A future PF activation series must implement PF-side
handling for the MAC-address and packet-filter mailbox requests, including
the VF policy checks, before wiring that callback.

Assisted-by: LLM
Signed-off-by: Satish Kharat <satishkh@xxxxxxxxx>
---
drivers/net/ethernet/cisco/enic/enic.h | 9 +
drivers/net/ethernet/cisco/enic/enic_mbox.c | 492 +++++++++++++++++++++++++++-
drivers/net/ethernet/cisco/enic/enic_mbox.h | 82 +++++
3 files changed, 565 insertions(+), 18 deletions(-)

diff --git a/drivers/net/ethernet/cisco/enic/enic.h b/drivers/net/ethernet/cisco/enic/enic.h
index 782b8b1843ab..45992b355501 100644
--- a/drivers/net/ethernet/cisco/enic/enic.h
+++ b/drivers/net/ethernet/cisco/enic/enic.h
@@ -239,6 +239,8 @@ enum enic_vf_type {
};

/* Per-instance private data structure */
+struct enic_mac_addr;
+
struct enic {
struct net_device *netdev;
struct pci_dev *pdev;
@@ -360,6 +362,13 @@ struct enic {
unsigned int vf_ack_count;
u64 mbox_expected_msg_num;
u8 mbox_expected_reply;
+ int mbox_reply_status;
+ u16 mbox_reply_filter_flags;
+ /* The request mutex keeps this caller-owned reply array alive until the
+ * matching reply handler has copied all per-address result flags.
+ */
+ struct enic_mac_addr *mbox_reply_mac_addrs;
+ u16 mbox_reply_mac_count;
bool mbox_initialized;

/* PF: per-VF MBOX state, allocated when SRIOV V2 is enabled */
diff --git a/drivers/net/ethernet/cisco/enic/enic_mbox.c b/drivers/net/ethernet/cisco/enic/enic_mbox.c
index 2d77d41577a3..3f73072170b9 100644
--- a/drivers/net/ethernet/cisco/enic/enic_mbox.c
+++ b/drivers/net/ethernet/cisco/enic/enic_mbox.c
@@ -6,6 +6,7 @@
#include <linux/dma-mapping.h>
#include <linux/delay.h>
#include <linux/completion.h>
+#include <linux/etherdevice.h>

#include "vnic_dev.h"
#include "vnic_wq.h"
@@ -376,6 +377,82 @@ static void enic_mbox_vf_request_finish(struct enic *enic)
mutex_unlock(&enic->vf_mbox_request_lock);
}

+/* Return with mbox_state_lock held when this handler owns the reply. */
+static bool enic_mbox_vf_reply_claim(struct enic *enic, u8 reply_type,
+ u64 msg_num, u8 *expected)
+{
+ spin_lock_bh(&enic->mbox_state_lock);
+ *expected = enic->mbox_expected_reply;
+ if (*expected == reply_type &&
+ enic->mbox_expected_msg_num == msg_num)
+ return true;
+ spin_unlock_bh(&enic->mbox_state_lock);
+
+ return false;
+}
+
+enum enic_mbox_vf_reply_recovery {
+ ENIC_MBOX_VF_REPLY_OK,
+ ENIC_MBOX_VF_REPLY_RECONNECT,
+ ENIC_MBOX_VF_REPLY_REGISTRATION_LOST,
+};
+
+static int
+enic_mbox_vf_classify_reply(bool malformed, u16 ret_major,
+ enum enic_mbox_vf_reply_recovery *recovery)
+{
+ *recovery = ENIC_MBOX_VF_REPLY_OK;
+ if (malformed) {
+ *recovery = ENIC_MBOX_VF_REPLY_RECONNECT;
+ return -EIO;
+ }
+ /* Some deployed peers return a negative errno in this 16-bit field.
+ * Interpret protocol bits only when no unknown bits are present; otherwise
+ * an errno such as -EINVAL could accidentally look like registration loss.
+ */
+ if (!(ret_major & ~ENIC_MBOX_ERR_MASK) &&
+ (ret_major & ENIC_MBOX_ERR_VF_NOT_REGISTERED)) {
+ *recovery = ENIC_MBOX_VF_REPLY_REGISTRATION_LOST;
+ return -ENODEV;
+ }
+ if (!(ret_major & ~ENIC_MBOX_ERR_MASK) &&
+ (ret_major & ENIC_MBOX_ERR_MSG_NOT_SUPPORTED))
+ return -EOPNOTSUPP;
+ if (ret_major)
+ return -EIO;
+
+ return 0;
+}
+
+static void
+enic_mbox_vf_recover_reply_locked(struct enic *enic,
+ enum enic_mbox_vf_reply_recovery recovery)
+{
+ bool registration_lost;
+
+ lockdep_assert_held(&enic->mbox_state_lock);
+
+ if (recovery != ENIC_MBOX_VF_REPLY_OK) {
+ registration_lost =
+ recovery == ENIC_MBOX_VF_REPLY_REGISTRATION_LOST;
+ enic_mbox_vf_mark_reconnect_locked(enic, registration_lost);
+ }
+}
+
+static void enic_mbox_vf_reply_complete(struct enic *enic)
+{
+ lockdep_assert_held(&enic->mbox_state_lock);
+ enic->mbox_expected_reply = 0;
+ enic->mbox_expected_msg_num = 0;
+ /* Publish completion before releasing the state lock. A waiter that
+ * hit the timeout boundary may otherwise see the claimed state, finish the
+ * request, and let a new request reinitialize this completion before the
+ * old handler signals it.
+ */
+ complete(&enic->mbox_comp);
+ spin_unlock_bh(&enic->mbox_state_lock);
+}
+
int enic_mbox_send_link_state(struct enic *enic, u16 vf_id, u32 link_state)
{
struct enic_mbox_pf_link_state_notif_msg notif = {};
@@ -551,23 +628,21 @@ static void enic_mbox_vf_handle_reply(struct enic *enic, u8 reply_type,
void *payload, u64 msg_num)
{
struct enic_mbox_generic_reply *reply = payload;
+ enum enic_mbox_vf_reply_recovery recovery;
u16 ret_major = le16_to_cpu(reply->ret_major);
- u64 expected_msg_num;
- u8 expected_type;
+ u8 expected;
+ int status;

- spin_lock_bh(&enic->mbox_state_lock);
- expected_type = enic->mbox_expected_reply;
- expected_msg_num = enic->mbox_expected_msg_num;
- if (expected_type != reply_type || expected_msg_num != msg_num) {
- spin_unlock_bh(&enic->mbox_state_lock);
+ status = enic_mbox_vf_classify_reply(false, ret_major, &recovery);
+ if (!enic_mbox_vf_reply_claim(enic, reply_type, msg_num, &expected)) {
netdev_warn(enic->netdev,
- "MBOX: stale reply %u/%llu (expected %u/%llu), drop\n",
+ "MBOX: stale reply %u/%llu (expected %u), drop\n",
reply_type, (unsigned long long)msg_num,
- expected_type, (unsigned long long)expected_msg_num);
+ expected);
return;
}

- if (!ret_major) {
+ if (!status) {
switch (reply_type) {
case ENIC_MBOX_VF_CAPABILITY_REPLY: {
struct enic_mbox_vf_capability_reply_msg *cap = payload;
@@ -584,16 +659,168 @@ static void enic_mbox_vf_handle_reply(struct enic *enic, u8 reply_type,
break;
}
}
- enic->mbox_expected_reply = 0;
- enic->mbox_expected_msg_num = 0;
- complete(&enic->mbox_comp);
- spin_unlock_bh(&enic->mbox_state_lock);
+ enic_mbox_vf_recover_reply_locked(enic, recovery);
+ WRITE_ONCE(enic->mbox_reply_status, status);
+ enic_mbox_vf_reply_complete(enic);

if (ret_major)
netdev_warn(enic->netdev,
"MBOX: PF rejected reply type %u: %u/%u\n",
reply_type, ret_major,
le16_to_cpu(reply->ret_minor));
+ if (recovery != ENIC_MBOX_VF_REPLY_OK)
+ enic_mbox_vf_kick_recovery(enic);
+}
+
+static bool enic_mbox_vf_mac_reply_matches(const struct enic_mac_addr *request,
+ const struct enic_mac_addr *reply)
+{
+ u16 request_flags = le16_to_cpu(request->flags);
+ u16 reply_flags = le16_to_cpu(reply->flags);
+ u16 idempotent_result = reply_flags &
+ (ENIC_MAC_ADDR_FLAG_DUPLICATE |
+ ENIC_MAC_ADDR_FLAG_NOT_FOUND);
+ u16 result = reply_flags & ENIC_MAC_ADDR_FLAG_REPLY_MASK;
+ u16 expected_result;
+
+ if (!ether_addr_equal(request->addr, reply->addr) ||
+ (request_flags & ENIC_MAC_ADDR_FLAG_REQUEST_MASK) !=
+ (reply_flags & ENIC_MAC_ADDR_FLAG_REQUEST_MASK))
+ return false;
+ if (hweight16(result) > 1)
+ return false;
+
+ /* DUPLICATE is a successful ADD result and NOT_FOUND is a successful
+ * DELETE result. Neither is valid for the opposite operation, and a
+ * reply cannot report both outcomes for one entry.
+ */
+ expected_result = request_flags & ENIC_MAC_ADDR_FLAG_ADD ?
+ ENIC_MAC_ADDR_FLAG_DUPLICATE :
+ ENIC_MAC_ADDR_FLAG_NOT_FOUND;
+
+ return !idempotent_result || idempotent_result == expected_result;
+}
+
+static void enic_mbox_vf_handle_add_del_mac_reply(struct enic *enic,
+ void *payload, u16 msg_len,
+ u64 msg_num)
+{
+ struct enic_mbox_vf_add_del_mac_reply_msg *reply = payload;
+ enum enic_mbox_vf_reply_recovery recovery;
+ u16 reported_errors = 0;
+ u16 num_addrs = 0;
+ u16 ret_minor = 0;
+ u16 ret_major = 0;
+ u8 expected;
+ unsigned int i;
+ int status;
+
+ if (msg_len < sizeof(*reply)) {
+ status = enic_mbox_vf_classify_reply(true, 0, &recovery);
+ } else {
+ ret_major = le16_to_cpu(reply->reply.ret_major);
+ ret_minor = le16_to_cpu(reply->reply.ret_minor);
+ status = enic_mbox_vf_classify_reply(false, ret_major,
+ &recovery);
+ if (status == -EIO)
+ recovery = ENIC_MBOX_VF_REPLY_RECONNECT;
+ }
+ if (status)
+ goto claim;
+
+ num_addrs = le16_to_cpu(reply->num_addrs);
+ if (!num_addrs || num_addrs > ENIC_MBOX_MAX_MAC_OPS ||
+ struct_size(reply, mac_addr, num_addrs) > msg_len) {
+ status = enic_mbox_vf_classify_reply(true, 0, &recovery);
+ goto claim;
+ }
+
+claim:
+ if (!enic_mbox_vf_reply_claim(enic, ENIC_MBOX_VF_ADD_DEL_MAC_REPLY,
+ msg_num, &expected))
+ return;
+ if (!status &&
+ (num_addrs != enic->mbox_reply_mac_count ||
+ !enic->mbox_reply_mac_addrs)) {
+ status = enic_mbox_vf_classify_reply(true, 0, &recovery);
+ } else if (!status) {
+ /* A detailed reply corresponds entry-for-entry with the request.
+ * Validate the echoed request fields and operation-specific results
+ * before exposing result flags to the waiting caller.
+ */
+ for (i = 0; i < num_addrs; i++) {
+ struct enic_mac_addr *request =
+ &enic->mbox_reply_mac_addrs[i];
+ u16 flags = le16_to_cpu(reply->mac_addr[i].flags);
+
+ if (!enic_mbox_vf_mac_reply_matches(request,
+ &reply->mac_addr[i])) {
+ status = enic_mbox_vf_classify_reply(true, 0,
+ &recovery);
+ break;
+ }
+ if (flags & ENIC_MAC_ADDR_FLAG_INDETERMINATE_MASK) {
+ status = -EIO;
+ recovery = ENIC_MBOX_VF_REPLY_RECONNECT;
+ break;
+ }
+ if ((flags & ENIC_MAC_ADDR_FLAG_REPLY_MASK) &&
+ !(flags & ENIC_MAC_ADDR_FLAG_SKIPPED))
+ reported_errors++;
+ }
+ if (!status && reported_errors != ret_minor)
+ status = enic_mbox_vf_classify_reply(true, 0,
+ &recovery);
+
+ if (!status)
+ for (i = 0; i < num_addrs; i++)
+ enic->mbox_reply_mac_addrs[i].flags =
+ reply->mac_addr[i].flags;
+ }
+ /* After a malformed reply, the VF cannot trust that its state matches the
+ * PF. VF_NOT_REGISTERED means the PF removed all VF state. Both require a
+ * new registration; an ordinary policy rejection does not.
+ */
+ enic_mbox_vf_recover_reply_locked(enic, recovery);
+ WRITE_ONCE(enic->mbox_reply_status, status);
+ enic_mbox_vf_reply_complete(enic);
+ if (recovery != ENIC_MBOX_VF_REPLY_OK)
+ enic_mbox_vf_kick_recovery(enic);
+}
+
+static void enic_mbox_vf_handle_set_pkt_filter_reply(struct enic *enic,
+ void *payload, u16 msg_len,
+ u64 msg_num)
+{
+ struct enic_mbox_vf_set_pkt_filter_reply_msg *reply = payload;
+ enum enic_mbox_vf_reply_recovery recovery;
+ u16 applied = 0;
+ u16 ret_major = 0;
+ u8 expected;
+ int status;
+
+ if (msg_len < sizeof(*reply)) {
+ status = enic_mbox_vf_classify_reply(true, 0, &recovery);
+ } else {
+ ret_major = le16_to_cpu(reply->reply.ret_major);
+ status = enic_mbox_vf_classify_reply(false, ret_major,
+ &recovery);
+ if (status && (ret_major & ~ENIC_MBOX_ERR_MASK))
+ recovery = ENIC_MBOX_VF_REPLY_RECONNECT;
+ }
+ if (!status)
+ applied = le16_to_cpu(reply->reply.ret_minor);
+
+ if (!enic_mbox_vf_reply_claim(enic,
+ ENIC_MBOX_VF_SET_PKT_FILTER_REPLY,
+ msg_num, &expected))
+ return;
+ enic_mbox_vf_recover_reply_locked(enic, recovery);
+ WRITE_ONCE(enic->mbox_reply_status, status);
+ WRITE_ONCE(enic->mbox_reply_filter_flags, applied);
+ enic_mbox_vf_reply_complete(enic);
+ if (recovery != ENIC_MBOX_VF_REPLY_OK)
+ enic_mbox_vf_kick_recovery(enic);
}

static void enic_mbox_vf_handle_link_state(struct enic *enic, void *payload,
@@ -679,6 +906,34 @@ static bool enic_mbox_vf_payload_ok(struct enic *enic, u8 msg_type,
return true;
}

+static void enic_mbox_vf_malformed_msg(struct enic *enic, u8 msg_type,
+ u64 msg_num)
+{
+ u8 expected;
+
+ switch (msg_type) {
+ case ENIC_MBOX_PF_LINK_STATE_NOTIF:
+ case ENIC_MBOX_PF_SET_ADMIN_MAC_NOTIF:
+ enic_mbox_vf_require_reconnect(enic);
+ return;
+ case ENIC_MBOX_VF_CAPABILITY_REPLY:
+ case ENIC_MBOX_VF_REGISTER_REPLY:
+ case ENIC_MBOX_VF_UNREGISTER_REPLY:
+ case ENIC_MBOX_VF_ADD_DEL_MAC_REPLY:
+ case ENIC_MBOX_VF_SET_PKT_FILTER_REPLY:
+ break;
+ default:
+ return;
+ }
+
+ if (!enic_mbox_vf_reply_claim(enic, msg_type, msg_num, &expected))
+ return;
+ enic_mbox_vf_mark_reconnect_locked(enic, false);
+ WRITE_ONCE(enic->mbox_reply_status, -EIO);
+ enic_mbox_vf_reply_complete(enic);
+ enic_mbox_vf_kick_recovery(enic);
+}
+
static void enic_mbox_vf_process_msg(struct enic *enic,
struct enic_mbox_hdr *hdr, void *payload,
u16 payload_len)
@@ -690,8 +945,10 @@ static void enic_mbox_vf_process_msg(struct enic *enic,
size_t exp = sizeof(struct enic_mbox_vf_capability_reply_msg);

if (!enic_mbox_vf_payload_ok(enic, hdr->msg_type,
- payload_len, exp))
+ payload_len, exp)) {
+ enic_mbox_vf_malformed_msg(enic, hdr->msg_type, msg_num);
return;
+ }
enic_mbox_vf_handle_reply(enic, hdr->msg_type, payload, msg_num);
break;
}
@@ -699,8 +956,10 @@ static void enic_mbox_vf_process_msg(struct enic *enic,
size_t exp = sizeof(struct enic_mbox_vf_register_reply_msg);

if (!enic_mbox_vf_payload_ok(enic, hdr->msg_type,
- payload_len, exp))
+ payload_len, exp)) {
+ enic_mbox_vf_malformed_msg(enic, hdr->msg_type, msg_num);
return;
+ }
enic_mbox_vf_handle_reply(enic, hdr->msg_type, payload, msg_num);
break;
}
@@ -708,8 +967,10 @@ static void enic_mbox_vf_process_msg(struct enic *enic,
size_t exp = sizeof(struct enic_mbox_vf_register_reply_msg);

if (!enic_mbox_vf_payload_ok(enic, hdr->msg_type,
- payload_len, exp))
+ payload_len, exp)) {
+ enic_mbox_vf_malformed_msg(enic, hdr->msg_type, msg_num);
return;
+ }
enic_mbox_vf_handle_reply(enic, hdr->msg_type, payload, msg_num);
break;
}
@@ -717,11 +978,21 @@ static void enic_mbox_vf_process_msg(struct enic *enic,
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))
+ payload_len, exp)) {
+ enic_mbox_vf_malformed_msg(enic, hdr->msg_type, msg_num);
return;
+ }
enic_mbox_vf_handle_link_state(enic, payload, msg_num);
break;
}
+ case ENIC_MBOX_VF_ADD_DEL_MAC_REPLY:
+ enic_mbox_vf_handle_add_del_mac_reply(enic, payload,
+ payload_len, msg_num);
+ break;
+ case ENIC_MBOX_VF_SET_PKT_FILTER_REPLY:
+ enic_mbox_vf_handle_set_pkt_filter_reply(enic, payload,
+ payload_len, msg_num);
+ break;
default:
netdev_dbg(enic->netdev,
"MBOX: VF unhandled msg type %u\n",
@@ -761,6 +1032,10 @@ static void enic_mbox_recv_handler(struct enic *enic, void *buf,
netdev_warn(enic->netdev,
"MBOX: invalid msg_len %u (buf len %u)\n",
msg_len, len);
+ if (!enic->vf_state &&
+ le16_to_cpu(hdr->src_vnic_id) == ENIC_MBOX_DST_PF)
+ enic_mbox_vf_malformed_msg(enic, hdr->msg_type,
+ le64_to_cpu(hdr->msg_num));
return;
}

@@ -791,10 +1066,12 @@ int enic_mbox_vf_capability_check(struct enic *enic)
{
struct enic_mbox_vf_capability_msg req = {};
u32 version;
+ int status;
int err;

enic_mbox_vf_request_start(enic);
WRITE_ONCE(enic->pf_cap_version, 0);
+ WRITE_ONCE(enic->mbox_reply_status, 0);
req.version = cpu_to_le32(ENIC_MBOX_CAP_VERSION_1);

err = enic_mbox_vf_send_request(enic,
@@ -808,12 +1085,15 @@ int enic_mbox_vf_capability_check(struct enic *enic)

err = enic_mbox_wait_reply(enic, 3000);
version = READ_ONCE(enic->pf_cap_version);
+ status = READ_ONCE(enic->mbox_reply_status);
enic_mbox_vf_request_finish(enic);
if (err) {
netdev_warn(enic->netdev,
"MBOX: no capability reply from PF\n");
return err;
}
+ if (status)
+ return status;

if (version < ENIC_MBOX_CAP_VERSION_1) {
netdev_warn(enic->netdev,
@@ -828,10 +1108,12 @@ int enic_mbox_vf_capability_check(struct enic *enic)
int enic_mbox_vf_register(struct enic *enic)
{
bool registered;
+ int status;
int err;

enic_mbox_vf_request_start(enic);
WRITE_ONCE(enic->vf_registered, false);
+ WRITE_ONCE(enic->mbox_reply_status, 0);

err = enic_mbox_vf_send_request(enic, ENIC_MBOX_VF_REGISTER_REQUEST,
ENIC_MBOX_VF_REGISTER_REPLY, NULL, 0);
@@ -842,12 +1124,15 @@ int enic_mbox_vf_register(struct enic *enic)

err = enic_mbox_wait_reply(enic, 3000);
registered = READ_ONCE(enic->vf_registered);
+ status = READ_ONCE(enic->mbox_reply_status);
enic_mbox_vf_request_finish(enic);
if (err) {
netdev_warn(enic->netdev,
"MBOX: VF registration with PF timed out\n");
return err;
}
+ if (status)
+ return status;

if (!registered)
return -ENODEV;
@@ -858,15 +1143,18 @@ int enic_mbox_vf_register(struct enic *enic)
int enic_mbox_vf_unregister(struct enic *enic)
{
bool registered;
+ int status;
int err;

if (!READ_ONCE(enic->vf_registered))
return 0;
+
enic_mbox_vf_request_start(enic);
if (!READ_ONCE(enic->vf_registered)) {
enic_mbox_vf_request_finish(enic);
return 0;
}
+ WRITE_ONCE(enic->mbox_reply_status, 0);

err = enic_mbox_vf_send_request(enic,
ENIC_MBOX_VF_UNREGISTER_REQUEST,
@@ -879,14 +1167,182 @@ int enic_mbox_vf_unregister(struct enic *enic)

err = enic_mbox_wait_reply(enic, 3000);
registered = READ_ONCE(enic->vf_registered);
+ status = READ_ONCE(enic->mbox_reply_status);
enic_mbox_vf_request_finish(enic);
if (err)
return err;
+ if (status)
+ return status;
if (registered)
return -EACCES;
return 0;
}

+int enic_mbox_vf_add_del_macs(struct enic *enic,
+ struct enic_mac_addr *macs, u16 num_macs)
+{
+ struct enic_mbox_vf_add_del_mac_msg *req;
+ unsigned int i;
+ int status;
+ int err;
+
+ if (!READ_ONCE(enic->vf_registered) || !enic->has_admin_channel)
+ return -ENODEV;
+ if (!num_macs || num_macs > ENIC_MBOX_MAX_MAC_OPS)
+ return -EINVAL;
+
+ req = kzalloc_flex(*req, mac_addr, num_macs);
+ if (!req)
+ return -ENOMEM;
+
+ req->num_addrs = cpu_to_le16(num_macs);
+ for (i = 0; i < num_macs; i++)
+ req->mac_addr[i] = macs[i];
+
+ enic_mbox_vf_request_start(enic);
+ if (!READ_ONCE(enic->vf_registered) || !enic->has_admin_channel) {
+ err = -ENODEV;
+ } else {
+ spin_lock_bh(&enic->mbox_state_lock);
+ enic->mbox_reply_mac_addrs = macs;
+ enic->mbox_reply_mac_count = num_macs;
+ spin_unlock_bh(&enic->mbox_state_lock);
+ WRITE_ONCE(enic->mbox_reply_status, 0);
+ err = enic_mbox_vf_send_request(enic,
+ ENIC_MBOX_VF_ADD_DEL_MAC_REQUEST,
+ ENIC_MBOX_VF_ADD_DEL_MAC_REPLY,
+ req,
+ struct_size(req, mac_addr,
+ num_macs));
+ }
+ kfree(req);
+ if (err) {
+ spin_lock_bh(&enic->mbox_state_lock);
+ enic->mbox_reply_mac_addrs = NULL;
+ enic->mbox_reply_mac_count = 0;
+ spin_unlock_bh(&enic->mbox_state_lock);
+ enic_mbox_vf_request_abort(enic);
+ return err;
+ }
+
+ err = enic_mbox_wait_reply(enic, 3000);
+ status = READ_ONCE(enic->mbox_reply_status);
+ spin_lock_bh(&enic->mbox_state_lock);
+ enic->mbox_reply_mac_addrs = NULL;
+ enic->mbox_reply_mac_count = 0;
+ spin_unlock_bh(&enic->mbox_state_lock);
+ if (err) {
+ /* The PF may have updated its software ledger before a hardware
+ * failure whose reply was lost. A repeated idempotent operation could
+ * then appear converged while hardware state is stale, so replay from
+ * a fresh registration generation.
+ */
+ enic_mbox_vf_require_reconnect(enic);
+ enic_mbox_vf_request_finish(enic);
+ return err;
+ }
+ enic_mbox_vf_request_finish(enic);
+
+ return status;
+}
+
+int enic_mbox_vf_add_del_mac(struct enic *enic, const u8 *addr, bool add,
+ bool station)
+{
+ struct enic_mac_addr mac = {};
+ u16 flags = 0;
+ int err;
+
+ ether_addr_copy(mac.addr, addr);
+ if (add)
+ flags |= ENIC_MAC_ADDR_FLAG_ADD;
+ if (station)
+ flags |= ENIC_MAC_ADDR_FLAG_STATION;
+ mac.flags = cpu_to_le16(flags);
+
+ err = enic_mbox_vf_add_del_macs(enic, &mac, 1);
+ if (err)
+ return err;
+ if (le16_to_cpu(mac.flags) & ENIC_MAC_ADDR_FLAG_ERROR_MASK)
+ return -EACCES;
+
+ return 0;
+}
+
+int enic_mbox_vf_set_pkt_filter(struct enic *enic, int directed,
+ int multicast, int broadcast,
+ int promisc, int allmulti, u16 *applied_flags)
+{
+ struct enic_mbox_vf_set_pkt_filter_msg req = {};
+ u16 applied;
+ u16 flags = 0;
+ u16 required;
+ int status;
+ int err;
+
+ if (!READ_ONCE(enic->vf_registered) || !enic->has_admin_channel)
+ return -ENODEV;
+
+ if (directed)
+ flags |= CMD_PFILTER_DIRECTED;
+ if (multicast)
+ flags |= CMD_PFILTER_MULTICAST;
+ if (broadcast)
+ flags |= CMD_PFILTER_BROADCAST;
+ if (promisc)
+ flags |= CMD_PFILTER_PROMISCUOUS;
+ if (allmulti)
+ flags |= CMD_PFILTER_ALL_MULTICAST;
+ req.flags = cpu_to_le16(flags);
+
+ enic_mbox_vf_request_start(enic);
+ if (!READ_ONCE(enic->vf_registered) || !enic->has_admin_channel) {
+ enic_mbox_vf_request_abort(enic);
+ return -ENODEV;
+ }
+ WRITE_ONCE(enic->mbox_reply_status, 0);
+ WRITE_ONCE(enic->mbox_reply_filter_flags, 0);
+
+ err = enic_mbox_vf_send_request(enic,
+ ENIC_MBOX_VF_SET_PKT_FILTER_REQUEST,
+ ENIC_MBOX_VF_SET_PKT_FILTER_REPLY,
+ &req, sizeof(req));
+ if (err) {
+ enic_mbox_vf_request_abort(enic);
+ return err;
+ }
+
+ err = enic_mbox_wait_reply(enic, 3000);
+ status = READ_ONCE(enic->mbox_reply_status);
+ if (!err && !status) {
+ applied = READ_ONCE(enic->mbox_reply_filter_flags);
+ /* Directed, multicast, and broadcast are not policy-gated. The PF
+ * may only withhold the two broad receive modes, and may add directed
+ * reception because it is mandatory for a usable VF.
+ */
+ required = (flags | CMD_PFILTER_DIRECTED) &
+ ~(CMD_PFILTER_PROMISCUOUS |
+ CMD_PFILTER_ALL_MULTICAST);
+ if ((applied & ~(flags | CMD_PFILTER_DIRECTED)) ||
+ (applied & required) != required) {
+ netdev_warn(enic->netdev,
+ "MBOX: invalid packet filter reply %#x for request %#x\n",
+ applied, flags);
+ enic_mbox_vf_require_reconnect(enic);
+ status = -EIO;
+ } else if (applied_flags) {
+ *applied_flags = applied;
+ }
+ }
+ if (err)
+ enic_mbox_vf_require_reconnect(enic);
+ enic_mbox_vf_request_finish(enic);
+ if (err)
+ return err;
+
+ return status;
+}
+
void enic_mbox_init(struct enic *enic)
{
bool reinit = enic->mbox_initialized;
diff --git a/drivers/net/ethernet/cisco/enic/enic_mbox.h b/drivers/net/ethernet/cisco/enic/enic_mbox.h
index 37bc41a900f4..5eca3a25671e 100644
--- a/drivers/net/ethernet/cisco/enic/enic_mbox.h
+++ b/drivers/net/ethernet/cisco/enic/enic_mbox.h
@@ -5,6 +5,7 @@
#define _ENIC_MBOX_H_

#include <linux/bits.h>
+#include <linux/if_ether.h>
#include <linux/types.h>

/*
@@ -22,6 +23,12 @@ enum enic_mbox_msg_type {
ENIC_MBOX_VF_UNREGISTER_REPLY = 5,
ENIC_MBOX_PF_LINK_STATE_NOTIF = 6,
ENIC_MBOX_PF_LINK_STATE_ACK = 7,
+ ENIC_MBOX_VF_ADD_DEL_MAC_REQUEST = 10,
+ ENIC_MBOX_VF_ADD_DEL_MAC_REPLY = 11,
+ ENIC_MBOX_PF_SET_ADMIN_MAC_NOTIF = 12,
+ ENIC_MBOX_PF_SET_ADMIN_MAC_ACK = 13,
+ ENIC_MBOX_VF_SET_PKT_FILTER_REQUEST = 14,
+ ENIC_MBOX_VF_SET_PKT_FILTER_REPLY = 15,
ENIC_MBOX_MAX
};

@@ -42,6 +49,9 @@ struct enic_mbox_generic_reply {
#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)
+#define ENIC_MBOX_ERR_MASK (ENIC_MBOX_ERR_GENERIC | \
+ ENIC_MBOX_ERR_VF_NOT_REGISTERED | \
+ ENIC_MBOX_ERR_MSG_NOT_SUPPORTED)

/* ENIC_MBOX_VF_CAPABILITY_REQUEST / _REPLY */
#define ENIC_MBOX_CAP_VERSION_0 0
@@ -80,6 +90,71 @@ struct enic_mbox_pf_link_state_ack_msg {
struct enic_mbox_generic_reply ack;
};

+/* ENIC_MBOX_PF_SET_ADMIN_MAC_NOTIF / _ACK */
+struct enic_mbox_pf_set_admin_mac_notif_msg {
+ u8 mac_addr[ETH_ALEN];
+ __le16 pad;
+};
+
+/* ENIC_MBOX_VF_ADD_DEL_MAC_REQUEST / _REPLY */
+#define ENIC_MAC_ADDR_FLAG_ADD BIT(0)
+#define ENIC_MAC_ADDR_FLAG_STATION BIT(1)
+#define ENIC_MAC_ADDR_FLAG_OVERFLOW BIT(8)
+#define ENIC_MAC_ADDR_FLAG_DUPLICATE BIT(9)
+#define ENIC_MAC_ADDR_FLAG_FAILED BIT(10)
+#define ENIC_MAC_ADDR_FLAG_NOT_FOUND BIT(11)
+#define ENIC_MAC_ADDR_FLAG_ERROR BIT(12)
+#define ENIC_MAC_ADDR_FLAG_NOT_PERMITTED BIT(13)
+#define ENIC_MAC_ADDR_FLAG_INVALID BIT(14)
+#define ENIC_MAC_ADDR_FLAG_SKIPPED BIT(15)
+
+#define ENIC_MAC_ADDR_FLAG_REQUEST_MASK GENMASK(7, 0)
+#define ENIC_MAC_ADDR_FLAG_REPLY_MASK GENMASK(15, 8)
+#define ENIC_MAC_ADDR_FLAG_INDETERMINATE_MASK \
+ (ENIC_MAC_ADDR_FLAG_FAILED | ENIC_MAC_ADDR_FLAG_ERROR)
+#define ENIC_MAC_ADDR_FLAG_PERMANENT_MASK \
+ (ENIC_MAC_ADDR_FLAG_OVERFLOW | ENIC_MAC_ADDR_FLAG_NOT_PERMITTED | \
+ ENIC_MAC_ADDR_FLAG_INVALID)
+#define ENIC_MAC_ADDR_FLAG_ERROR_MASK (ENIC_MAC_ADDR_FLAG_OVERFLOW | \
+ ENIC_MAC_ADDR_FLAG_FAILED | \
+ ENIC_MAC_ADDR_FLAG_ERROR | \
+ ENIC_MAC_ADDR_FLAG_NOT_PERMITTED | \
+ ENIC_MAC_ADDR_FLAG_INVALID | \
+ ENIC_MAC_ADDR_FLAG_SKIPPED)
+
+/* The protocol permits replacing all perfect filters and the station address
+ * in one request: one delete and one add operation for each address.
+ */
+#define ENIC_MBOX_MAX_MAC_OPS 130
+
+struct enic_mac_addr {
+ u8 addr[ETH_ALEN];
+ __le16 flags;
+};
+
+struct enic_mbox_vf_add_del_mac_msg {
+ __le16 num_addrs;
+ __le16 pad;
+ struct enic_mac_addr mac_addr[];
+};
+
+struct enic_mbox_vf_add_del_mac_reply_msg {
+ struct enic_mbox_generic_reply reply;
+ __le16 num_addrs;
+ __le16 pad;
+ struct enic_mac_addr mac_addr[];
+};
+
+/* ENIC_MBOX_VF_SET_PKT_FILTER_REQUEST / _REPLY */
+struct enic_mbox_vf_set_pkt_filter_msg {
+ __le16 flags;
+ __le16 pad;
+};
+
+struct enic_mbox_vf_set_pkt_filter_reply_msg {
+ struct enic_mbox_generic_reply reply;
+};
+
#define ENIC_MBOX_DST_PF 0xFFFF

struct enic;
@@ -95,5 +170,12 @@ void enic_mbox_vf_require_reconnect(struct enic *enic);
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);
+int enic_mbox_vf_add_del_macs(struct enic *enic,
+ struct enic_mac_addr *macs, u16 num_macs);
+int enic_mbox_vf_add_del_mac(struct enic *enic, const u8 *addr, bool add,
+ bool station);
+int enic_mbox_vf_set_pkt_filter(struct enic *enic, int directed, int multicast,
+ int broadcast, int promisc, int allmulti,
+ u16 *applied_flags);

#endif /* _ENIC_MBOX_H_ */

--
2.43.0