[PATCH net-next v2 4/6] enic: validate V2 VF configuration replies
From: Satish Kharat
Date: Tue Sep 29 2026 - 15:05:52 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. A coherent top-level MAC-operation
failure remains an ordinary error. Reserve reconnect for malformed or
indeterminate replies and explicit registration loss.
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 | 490 +++++++++++++++++++++++++++-
drivers/net/ethernet/cisco/enic/enic_mbox.h | 82 +++++
3 files changed, 563 insertions(+), 18 deletions(-)
diff --git a/drivers/net/ethernet/cisco/enic/enic.h b/drivers/net/ethernet/cisco/enic/enic.h
index 4285b63083cc..ef8332138c5a 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;
@@ -365,6 +367,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 30a5f6fda676..265b58cd39be 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"
@@ -377,6 +378,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 = {};
@@ -552,23 +629,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;
@@ -585,16 +660,166 @@ 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)
+ 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,
@@ -680,6 +905,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)
@@ -691,8 +944,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;
}
@@ -700,8 +955,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;
}
@@ -709,8 +966,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;
}
@@ -718,11 +977,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",
@@ -762,6 +1031,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;
}
@@ -792,10 +1065,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,
@@ -809,12 +1084,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,
@@ -829,10 +1107,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);
@@ -843,12 +1123,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;
@@ -859,15 +1142,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,
@@ -880,14 +1166,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