Commit e24431a2 authored by Paolo Abeni's avatar Paolo Abeni
Browse files

Merge branch 'eth-fbnic-add-devlink-dev-flash-support'

Lee Trager says:

====================
eth: fbnic: Add devlink dev flash support

fbnic supports updating firmware using signed PLDM images. PLDM images are
written into the flash. Flashing does not interrupt the operation of the
device.

V4: https://lore.kernel.org/netdev/20250510002851.3247880-1-lee@trager.us/T/#t
V3: https://lore.kernel.org/lkml/20241111043058.1251632-1-lee@trager.us/T/
V2: https://lore.kernel.org/all/20241022013941.3764567-1-lee@trager.us/
====================

Link: https://patch.msgid.link/20250512190109.2475614-1-lee@trager.us


Signed-off-by: default avatarPaolo Abeni <pabeni@redhat.com>
parents 10465365 82534f44
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -28,6 +28,17 @@ devlink dev info provides version information for all three components. In
addition to the version the hg commit hash of the build is included as a
separate entry.

Upgrading Firmware
------------------

fbnic supports updating firmware using signed PLDM images with devlink dev
flash. PLDM images are written into the flash. Flashing does not interrupt
the operation of the device.

On host boot the latest UEFI driver is always used, no explicit activation
is required. Firmware activation is required to run new control firmware. cmrt
firmware can only be activated by power cycling the NIC.

Statistics
----------

+1 −0
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ config FBNIC
	select NET_DEVLINK
	select PAGE_POOL
	select PHYLINK
	select PLDMFW
	help
	  This driver supports Meta Platforms Host Network Interface.

+2 −1
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@
struct fbnic_napi_vector;

#define FBNIC_MAX_NAPI_VECTORS		128u
#define FBNIC_MBX_CMPL_SLOTS		4

struct fbnic_dev {
	struct device *dev;
@@ -42,7 +43,7 @@ struct fbnic_dev {

	struct fbnic_fw_mbx mbx[FBNIC_IPC_MBX_INDICES];
	struct fbnic_fw_cap fw_cap;
	struct fbnic_fw_completion *cmpl_data;
	struct fbnic_fw_completion *cmpl_data[FBNIC_MBX_CMPL_SLOTS];
	/* Lock protecting Tx Mailbox queue to prevent possible races */
	spinlock_t fw_tx_lock;

+259 −1
Original line number Diff line number Diff line
@@ -3,10 +3,12 @@

#include <linux/unaligned.h>
#include <linux/pci.h>
#include <linux/pldmfw.h>
#include <linux/types.h>
#include <net/devlink.h>

#include "fbnic.h"
#include "fbnic_tlv.h"

#define FBNIC_SN_STR_LEN	24

@@ -109,8 +111,264 @@ static int fbnic_devlink_info_get(struct devlink *devlink,
	return 0;
}

static bool
fbnic_pldm_match_record(struct pldmfw *context, struct pldmfw_record *record)
{
	struct pldmfw_desc_tlv *desc;
	u32 anti_rollback_ver = 0;
	struct devlink *devlink;
	struct fbnic_dev *fbd;
	struct pci_dev *pdev;

	/* First, use the standard PCI matching function */
	if (!pldmfw_op_pci_match_record(context, record))
		return false;

	pdev = to_pci_dev(context->dev);
	fbd = pci_get_drvdata(pdev);
	devlink = priv_to_devlink(fbd);

	/* If PCI match is successful, check for vendor-specific descriptors */
	list_for_each_entry(desc, &record->descs, entry) {
		if (desc->type != PLDM_DESC_ID_VENDOR_DEFINED)
			continue;

		if (desc->size < 21 || desc->data[0] != 1 ||
		    desc->data[1] != 15)
			continue;

		if (memcmp(desc->data + 2, "AntiRollbackVer", 15) != 0)
			continue;

		anti_rollback_ver = get_unaligned_le32(desc->data + 17);
		break;
	}

	/* Compare versions and return error if they do not match */
	if (anti_rollback_ver < fbd->fw_cap.anti_rollback_version) {
		char buf[128];

		snprintf(buf, sizeof(buf),
			 "New firmware anti-rollback version (0x%x) is older than device version (0x%x)!",
			 anti_rollback_ver, fbd->fw_cap.anti_rollback_version);
		devlink_flash_update_status_notify(devlink, buf,
						   "Anti-Rollback", 0, 0);

		return false;
	}

	return true;
}

static int
fbnic_flash_start(struct fbnic_dev *fbd, struct pldmfw_component *component)
{
	struct fbnic_fw_completion *cmpl;
	int err;

	cmpl = kzalloc(sizeof(*cmpl), GFP_KERNEL);
	if (!cmpl)
		return -ENOMEM;

	fbnic_fw_init_cmpl(cmpl, FBNIC_TLV_MSG_ID_FW_START_UPGRADE_REQ);
	err = fbnic_fw_xmit_fw_start_upgrade(fbd, cmpl,
					     component->identifier,
					     component->component_size);
	if (err)
		goto cmpl_free;

	/* Wait for firmware to ack firmware upgrade start */
	if (wait_for_completion_timeout(&cmpl->done, 10 * HZ))
		err = cmpl->result;
	else
		err = -ETIMEDOUT;

	fbnic_fw_clear_cmpl(fbd, cmpl);
cmpl_free:
	fbnic_fw_put_cmpl(cmpl);

	return err;
}

static int
fbnic_flash_component(struct pldmfw *context,
		      struct pldmfw_component *component)
{
	const u8 *data = component->component_data;
	const u32 size = component->component_size;
	struct fbnic_fw_completion *cmpl;
	const char *component_name;
	struct devlink *devlink;
	struct fbnic_dev *fbd;
	struct pci_dev *pdev;
	u32 offset = 0;
	u32 length = 0;
	char buf[32];
	int err;

	pdev = to_pci_dev(context->dev);
	fbd = pci_get_drvdata(pdev);
	devlink = priv_to_devlink(fbd);

	switch (component->identifier) {
	case QSPI_SECTION_CMRT:
		component_name = "boot1";
		break;
	case QSPI_SECTION_CONTROL_FW:
		component_name = "boot2";
		break;
	case QSPI_SECTION_OPTION_ROM:
		component_name = "option-rom";
		break;
	default:
		snprintf(buf, sizeof(buf), "Unknown component ID %u!",
			 component->identifier);
		devlink_flash_update_status_notify(devlink, buf, NULL, 0,
						   size);
		return -EINVAL;
	}

	/* Once firmware receives the request to start upgrading it responds
	 * with two messages:
	 * 1. An ACK that it received the message and possible error code
	 *    indicating that an upgrade is not currently possible.
	 * 2. A request for the first chunk of data
	 *
	 * Setup completions for write before issuing the start message so
	 * the driver can catch both messages.
	 */
	cmpl = kzalloc(sizeof(*cmpl), GFP_KERNEL);
	if (!cmpl)
		return -ENOMEM;

	fbnic_fw_init_cmpl(cmpl, FBNIC_TLV_MSG_ID_FW_WRITE_CHUNK_REQ);
	err = fbnic_mbx_set_cmpl(fbd, cmpl);
	if (err)
		goto cmpl_free;

	devlink_flash_update_timeout_notify(devlink, "Initializing",
					    component_name, 15);
	err = fbnic_flash_start(fbd, component);
	if (err)
		goto err_no_msg;

	while (offset < size) {
		if (!wait_for_completion_timeout(&cmpl->done, 15 * HZ)) {
			err = -ETIMEDOUT;
			break;
		}

		err = cmpl->result;
		if (err)
			break;

		/* Verify firmware is requesting the next chunk in the seq. */
		if (cmpl->u.fw_update.offset != offset + length) {
			err = -EFAULT;
			break;
		}

		offset = cmpl->u.fw_update.offset;
		length = cmpl->u.fw_update.length;

		if (length > TLV_MAX_DATA || offset + length > size) {
			err = -EFAULT;
			break;
		}

		devlink_flash_update_status_notify(devlink, "Flashing",
						   component_name,
						   offset, size);

		/* Mailbox will set length to 0 once it receives the finish
		 * message.
		 */
		if (!length)
			continue;

		reinit_completion(&cmpl->done);
		err = fbnic_fw_xmit_fw_write_chunk(fbd, data, offset, length,
						   0);
		if (err)
			break;
	}

	if (err) {
		fbnic_fw_xmit_fw_write_chunk(fbd, NULL, 0, 0, err);
err_no_msg:
		snprintf(buf, sizeof(buf), "Mailbox encountered error %d!",
			 err);
		devlink_flash_update_status_notify(devlink, buf,
						   component_name, 0, 0);
	}

	fbnic_fw_clear_cmpl(fbd, cmpl);
cmpl_free:
	fbnic_fw_put_cmpl(cmpl);

	return err;
}

static const struct pldmfw_ops fbnic_pldmfw_ops = {
	.match_record = fbnic_pldm_match_record,
	.flash_component = fbnic_flash_component,
};

static int
fbnic_devlink_flash_update(struct devlink *devlink,
			   struct devlink_flash_update_params *params,
			   struct netlink_ext_ack *extack)
{
	struct fbnic_dev *fbd = devlink_priv(devlink);
	const struct firmware *fw = params->fw;
	struct device *dev = fbd->dev;
	struct pldmfw context;
	char *err_msg;
	int err;

	context.ops = &fbnic_pldmfw_ops;
	context.dev = dev;

	err = pldmfw_flash_image(&context, fw);
	if (err) {
		switch (err) {
		case -EINVAL:
			err_msg = "Invalid image";
			break;
		case -EOPNOTSUPP:
			err_msg = "Unsupported image";
			break;
		case -ENOMEM:
			err_msg = "Out of memory";
			break;
		case -EFAULT:
			err_msg = "Invalid header";
			break;
		case -ENOENT:
			err_msg = "No matching record";
			break;
		case -ENODEV:
			err_msg = "No matching device";
			break;
		case -ETIMEDOUT:
			err_msg = "Timed out waiting for reply";
			break;
		default:
			err_msg = "Unknown error";
			break;
		}

		NL_SET_ERR_MSG_FMT_MOD(extack,
				       "Failed to flash PLDM Image: %s (error: %d)",
				       err_msg, err);
	}

	return err;
}

static const struct devlink_ops fbnic_devlink_ops = {
	.info_get	= fbnic_devlink_info_get,
	.flash_update	= fbnic_devlink_flash_update,
};

void fbnic_devlink_free(struct fbnic_dev *fbd)
+275 −19
Original line number Diff line number Diff line
@@ -237,6 +237,44 @@ static int fbnic_mbx_map_tlv_msg(struct fbnic_dev *fbd,
	return err;
}

static int fbnic_mbx_set_cmpl_slot(struct fbnic_dev *fbd,
				   struct fbnic_fw_completion *cmpl_data)
{
	struct fbnic_fw_mbx *tx_mbx = &fbd->mbx[FBNIC_IPC_MBX_TX_IDX];
	int free = -EXFULL;
	int i;

	if (!tx_mbx->ready)
		return -ENODEV;

	for (i = 0; i < FBNIC_MBX_CMPL_SLOTS; i++) {
		if (!fbd->cmpl_data[i])
			free = i;
		else if (fbd->cmpl_data[i]->msg_type == cmpl_data->msg_type)
			return -EEXIST;
	}

	if (free == -EXFULL)
		return -EXFULL;

	fbd->cmpl_data[free] = cmpl_data;

	return 0;
}

static void fbnic_mbx_clear_cmpl_slot(struct fbnic_dev *fbd,
				      struct fbnic_fw_completion *cmpl_data)
{
	int i;

	for (i = 0; i < FBNIC_MBX_CMPL_SLOTS; i++) {
		if (fbd->cmpl_data[i] == cmpl_data) {
			fbd->cmpl_data[i] = NULL;
			break;
		}
	}
}

static void fbnic_mbx_process_tx_msgs(struct fbnic_dev *fbd)
{
	struct fbnic_fw_mbx *tx_mbx = &fbd->mbx[FBNIC_IPC_MBX_TX_IDX];
@@ -258,6 +296,19 @@ static void fbnic_mbx_process_tx_msgs(struct fbnic_dev *fbd)
	tx_mbx->head = head;
}

int fbnic_mbx_set_cmpl(struct fbnic_dev *fbd,
		       struct fbnic_fw_completion *cmpl_data)
{
	unsigned long flags;
	int err;

	spin_lock_irqsave(&fbd->fw_tx_lock, flags);
	err = fbnic_mbx_set_cmpl_slot(fbd, cmpl_data);
	spin_unlock_irqrestore(&fbd->fw_tx_lock, flags);

	return err;
}

static int fbnic_mbx_map_req_w_cmpl(struct fbnic_dev *fbd,
				    struct fbnic_tlv_msg *msg,
				    struct fbnic_fw_completion *cmpl_data)
@@ -266,23 +317,20 @@ static int fbnic_mbx_map_req_w_cmpl(struct fbnic_dev *fbd,
	int err;

	spin_lock_irqsave(&fbd->fw_tx_lock, flags);

	/* If we are already waiting on a completion then abort */
	if (cmpl_data && fbd->cmpl_data) {
		err = -EBUSY;
	if (cmpl_data) {
		err = fbnic_mbx_set_cmpl_slot(fbd, cmpl_data);
		if (err)
			goto unlock_mbx;
	}

	/* Record completion location and submit request */
	if (cmpl_data)
		fbd->cmpl_data = cmpl_data;

	err = fbnic_mbx_map_msg(fbd, FBNIC_IPC_MBX_TX_IDX, msg,
				le16_to_cpu(msg->hdr.len) * sizeof(u32), 1);

	/* If msg failed then clear completion data for next caller */
	/* If we successfully reserved a completion and msg failed
	 * then clear completion data for next caller
	 */
	if (err && cmpl_data)
		fbd->cmpl_data = NULL;
		fbnic_mbx_clear_cmpl_slot(fbd, cmpl_data);

unlock_mbx:
	spin_unlock_irqrestore(&fbd->fw_tx_lock, flags);
@@ -304,12 +352,18 @@ fbnic_fw_get_cmpl_by_type(struct fbnic_dev *fbd, u32 msg_type)
{
	struct fbnic_fw_completion *cmpl_data = NULL;
	unsigned long flags;
	int i;

	spin_lock_irqsave(&fbd->fw_tx_lock, flags);
	if (fbd->cmpl_data && fbd->cmpl_data->msg_type == msg_type) {
		cmpl_data = fbd->cmpl_data;
		kref_get(&fbd->cmpl_data->ref_count);
	for (i = 0; i < FBNIC_MBX_CMPL_SLOTS; i++) {
		if (fbd->cmpl_data[i] &&
		    fbd->cmpl_data[i]->msg_type == msg_type) {
			cmpl_data = fbd->cmpl_data[i];
			kref_get(&cmpl_data->ref_count);
			break;
		}
	}

	spin_unlock_irqrestore(&fbd->fw_tx_lock, flags);

	return cmpl_data;
@@ -464,6 +518,7 @@ static const struct fbnic_tlv_index fbnic_fw_cap_resp_index[] = {
	FBNIC_TLV_ATTR_U32(FBNIC_FW_CAP_RESP_UEFI_VERSION),
	FBNIC_TLV_ATTR_STRING(FBNIC_FW_CAP_RESP_UEFI_COMMIT_STR,
			      FBNIC_FW_CAP_RESP_COMMIT_MAX_SIZE),
	FBNIC_TLV_ATTR_U32(FBNIC_FW_CAP_RESP_ANTI_ROLLBACK_VERSION),
	FBNIC_TLV_ATTR_LAST
};

@@ -586,6 +641,9 @@ static int fbnic_fw_parse_cap_resp(void *opaque, struct fbnic_tlv_msg **results)
	if (results[FBNIC_FW_CAP_RESP_BMC_ALL_MULTI] || !bmc_present)
		fbd->fw_cap.all_multi = all_multi;

	fbd->fw_cap.anti_rollback_version =
		fta_get_uint(results, FBNIC_FW_CAP_RESP_ANTI_ROLLBACK_VERSION);

	return 0;
}

@@ -708,6 +766,188 @@ void fbnic_fw_check_heartbeat(struct fbnic_dev *fbd)
		dev_warn(fbd->dev, "Failed to send heartbeat message\n");
}

int fbnic_fw_xmit_fw_start_upgrade(struct fbnic_dev *fbd,
				   struct fbnic_fw_completion *cmpl_data,
				   unsigned int id, unsigned int len)
{
	struct fbnic_tlv_msg *msg;
	int err;

	if (!fbnic_fw_present(fbd))
		return -ENODEV;

	if (!len)
		return -EINVAL;

	msg = fbnic_tlv_msg_alloc(FBNIC_TLV_MSG_ID_FW_START_UPGRADE_REQ);
	if (!msg)
		return -ENOMEM;

	err = fbnic_tlv_attr_put_int(msg, FBNIC_FW_START_UPGRADE_SECTION, id);
	if (err)
		goto free_message;

	err = fbnic_tlv_attr_put_int(msg, FBNIC_FW_START_UPGRADE_IMAGE_LENGTH,
				     len);
	if (err)
		goto free_message;

	err = fbnic_mbx_map_req_w_cmpl(fbd, msg, cmpl_data);
	if (err)
		goto free_message;

	return 0;

free_message:
	free_page((unsigned long)msg);
	return err;
}

static const struct fbnic_tlv_index fbnic_fw_start_upgrade_resp_index[] = {
	FBNIC_TLV_ATTR_S32(FBNIC_FW_START_UPGRADE_ERROR),
	FBNIC_TLV_ATTR_LAST
};

static int fbnic_fw_parse_fw_start_upgrade_resp(void *opaque,
						struct fbnic_tlv_msg **results)
{
	struct fbnic_fw_completion *cmpl_data;
	struct fbnic_dev *fbd = opaque;
	u32 msg_type;
	s32 err;

	/* Verify we have a completion pointer */
	msg_type = FBNIC_TLV_MSG_ID_FW_START_UPGRADE_REQ;
	cmpl_data = fbnic_fw_get_cmpl_by_type(fbd, msg_type);
	if (!cmpl_data)
		return -ENOSPC;

	/* Check for errors */
	err = fta_get_sint(results, FBNIC_FW_START_UPGRADE_ERROR);

	cmpl_data->result = err;
	complete(&cmpl_data->done);
	fbnic_fw_put_cmpl(cmpl_data);

	return 0;
}

int fbnic_fw_xmit_fw_write_chunk(struct fbnic_dev *fbd,
				 const u8 *data, u32 offset, u16 length,
				 int cancel_error)
{
	struct fbnic_tlv_msg *msg;
	int err;

	msg = fbnic_tlv_msg_alloc(FBNIC_TLV_MSG_ID_FW_WRITE_CHUNK_RESP);
	if (!msg)
		return -ENOMEM;

	/* Report error to FW to cancel upgrade */
	if (cancel_error) {
		err = fbnic_tlv_attr_put_int(msg, FBNIC_FW_WRITE_CHUNK_ERROR,
					     cancel_error);
		if (err)
			goto free_message;
	}

	if (data) {
		err = fbnic_tlv_attr_put_int(msg, FBNIC_FW_WRITE_CHUNK_OFFSET,
					     offset);
		if (err)
			goto free_message;

		err = fbnic_tlv_attr_put_int(msg, FBNIC_FW_WRITE_CHUNK_LENGTH,
					     length);
		if (err)
			goto free_message;

		err = fbnic_tlv_attr_put_value(msg, FBNIC_FW_WRITE_CHUNK_DATA,
					       data + offset, length);
		if (err)
			goto free_message;
	}

	err = fbnic_mbx_map_tlv_msg(fbd, msg);
	if (err)
		goto free_message;

	return 0;

free_message:
	free_page((unsigned long)msg);
	return err;
}

static const struct fbnic_tlv_index fbnic_fw_write_chunk_req_index[] = {
	FBNIC_TLV_ATTR_U32(FBNIC_FW_WRITE_CHUNK_OFFSET),
	FBNIC_TLV_ATTR_U32(FBNIC_FW_WRITE_CHUNK_LENGTH),
	FBNIC_TLV_ATTR_LAST
};

static int fbnic_fw_parse_fw_write_chunk_req(void *opaque,
					     struct fbnic_tlv_msg **results)
{
	struct fbnic_fw_completion *cmpl_data;
	struct fbnic_dev *fbd = opaque;
	u32 msg_type;
	u32 offset;
	u32 length;

	/* Verify we have a completion pointer */
	msg_type = FBNIC_TLV_MSG_ID_FW_WRITE_CHUNK_REQ;
	cmpl_data = fbnic_fw_get_cmpl_by_type(fbd, msg_type);
	if (!cmpl_data)
		return -ENOSPC;

	/* Pull length/offset pair and mark it as complete */
	offset = fta_get_uint(results, FBNIC_FW_WRITE_CHUNK_OFFSET);
	length = fta_get_uint(results, FBNIC_FW_WRITE_CHUNK_LENGTH);
	cmpl_data->u.fw_update.offset = offset;
	cmpl_data->u.fw_update.length = length;

	complete(&cmpl_data->done);
	fbnic_fw_put_cmpl(cmpl_data);

	return 0;
}

static const struct fbnic_tlv_index fbnic_fw_finish_upgrade_req_index[] = {
	FBNIC_TLV_ATTR_S32(FBNIC_FW_FINISH_UPGRADE_ERROR),
	FBNIC_TLV_ATTR_LAST
};

static int fbnic_fw_parse_fw_finish_upgrade_req(void *opaque,
						struct fbnic_tlv_msg **results)
{
	struct fbnic_fw_completion *cmpl_data;
	struct fbnic_dev *fbd = opaque;
	u32 msg_type;
	s32 err;

	/* Verify we have a completion pointer */
	msg_type = FBNIC_TLV_MSG_ID_FW_WRITE_CHUNK_REQ;
	cmpl_data = fbnic_fw_get_cmpl_by_type(fbd, msg_type);
	if (!cmpl_data)
		return -ENOSPC;

	/* Check for errors */
	err = fta_get_sint(results, FBNIC_FW_FINISH_UPGRADE_ERROR);

	/* Close out update by incrementing offset by length which should
	 * match the total size of the component. Set length to 0 since no
	 * new chunks will be requested.
	 */
	cmpl_data->u.fw_update.offset += cmpl_data->u.fw_update.length;
	cmpl_data->u.fw_update.length = 0;

	cmpl_data->result = err;
	complete(&cmpl_data->done);
	fbnic_fw_put_cmpl(cmpl_data);

	return 0;
}

/**
 * fbnic_fw_xmit_tsene_read_msg - Create and transmit a sensor read request
 * @fbd: FBNIC device structure
@@ -792,6 +1032,15 @@ static const struct fbnic_tlv_parser fbnic_fw_tlv_parser[] = {
			 fbnic_fw_parse_ownership_resp),
	FBNIC_TLV_PARSER(HEARTBEAT_RESP, fbnic_heartbeat_resp_index,
			 fbnic_fw_parse_heartbeat_resp),
	FBNIC_TLV_PARSER(FW_START_UPGRADE_RESP,
			 fbnic_fw_start_upgrade_resp_index,
			 fbnic_fw_parse_fw_start_upgrade_resp),
	FBNIC_TLV_PARSER(FW_WRITE_CHUNK_REQ,
			 fbnic_fw_write_chunk_req_index,
			 fbnic_fw_parse_fw_write_chunk_req),
	FBNIC_TLV_PARSER(FW_FINISH_UPGRADE_REQ,
			 fbnic_fw_finish_upgrade_req_index,
			 fbnic_fw_parse_fw_finish_upgrade_req),
	FBNIC_TLV_PARSER(TSENE_READ_RESP,
			 fbnic_tsene_read_resp_index,
			 fbnic_fw_parse_tsene_read_resp),
@@ -921,10 +1170,16 @@ static void __fbnic_fw_evict_cmpl(struct fbnic_fw_completion *cmpl_data)

static void fbnic_mbx_evict_all_cmpl(struct fbnic_dev *fbd)
{
	if (fbd->cmpl_data) {
		__fbnic_fw_evict_cmpl(fbd->cmpl_data);
		fbd->cmpl_data = NULL;
	int i;

	for (i = 0; i < FBNIC_MBX_CMPL_SLOTS; i++) {
		struct fbnic_fw_completion *cmpl_data = fbd->cmpl_data[i];

		if (cmpl_data)
			__fbnic_fw_evict_cmpl(cmpl_data);
	}

	memset(fbd->cmpl_data, 0, sizeof(fbd->cmpl_data));
}

void fbnic_mbx_flush_tx(struct fbnic_dev *fbd)
@@ -985,12 +1240,13 @@ void fbnic_fw_init_cmpl(struct fbnic_fw_completion *fw_cmpl,
	kref_init(&fw_cmpl->ref_count);
}

void fbnic_fw_clear_compl(struct fbnic_dev *fbd)
void fbnic_fw_clear_cmpl(struct fbnic_dev *fbd,
			 struct fbnic_fw_completion *fw_cmpl)
{
	unsigned long flags;

	spin_lock_irqsave(&fbd->fw_tx_lock, flags);
	fbd->cmpl_data = NULL;
	fbnic_mbx_clear_cmpl_slot(fbd, fw_cmpl);
	spin_unlock_irqrestore(&fbd->fw_tx_lock, flags);
}

Loading