This adds driver support to utilize the kernel completion API. This allows the driver to block on a response from firmware. Initially fbnic_fw_completion only has support for updates, future patches will add additional features. Signed-off-by: Lee Trager <lee@xxxxxxxxx> --- drivers/net/ethernet/meta/fbnic/fbnic_fw.c | 263 +++++++++++++++++++++ drivers/net/ethernet/meta/fbnic/fbnic_fw.h | 64 ++++- 2 files changed, 323 insertions(+), 4 deletions(-) diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_fw.c b/drivers/net/ethernet/meta/fbnic/fbnic_fw.c index 8f7a2a19ddf8..d46afc38b4a5 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_fw.c +++ b/drivers/net/ethernet/meta/fbnic/fbnic_fw.c @@ -207,6 +207,38 @@ static int fbnic_mbx_map_tlv_msg(struct fbnic_dev *fbd, 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) +{ + unsigned long flags; + 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; + 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 (err && cmpl_data) + fbd->cmpl_data = NULL; + +unlock_mbx: + spin_unlock_irqrestore(&fbd->fw_tx_lock, flags); + + return err; +} + static void fbnic_mbx_process_tx_msgs(struct fbnic_dev *fbd) { struct fbnic_fw_mbx *tx_mbx = &fbd->mbx[FBNIC_IPC_MBX_TX_IDX]; @@ -651,6 +683,225 @@ void fbnic_fw_check_heartbeat(struct fbnic_dev *fbd) dev_warn(fbd->dev, "Failed to send heartbeat message\n"); } +/** + * fbnic_fw_xmit_fw_start_upgrade - Create and transmit a start update message + * @fbd: FBNIC device structure + * @cmpl_data: Completion data for upgrade process + * @id: Component ID + * @len: Length of FW update package data + * + * Return: zero on success, negative value on failure + * + * Asks the FW to prepare for starting a firmware upgrade + */ +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 (!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; + int err = 0; + + /* Verify we have a completion pointer */ + cmpl_data = READ_ONCE(fbd->cmpl_data); + if (!cmpl_data || + cmpl_data->msg_type != FBNIC_TLV_MSG_ID_FW_WRITE_CHUNK_REQ) + return -ENOSPC; + + /* Check for errors */ + get_signed_result(FBNIC_FW_START_UPGRADE_ERROR, err); + + cmpl_data->result = err; + complete(&cmpl_data->done); + + return 0; +} + +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 length = 0, offset = 0; + struct fbnic_tlv_msg *msg; + int err; + + /* Start by attempting to allocate a response to the message */ + msg = fbnic_tlv_msg_alloc(FBNIC_TLV_MSG_ID_FW_WRITE_CHUNK_RESP); + if (!msg) + return -ENOMEM; + + /* Verify we have a completion pointer */ + cmpl_data = READ_ONCE(fbd->cmpl_data); + if (!cmpl_data || + cmpl_data->msg_type != FBNIC_TLV_MSG_ID_FW_WRITE_CHUNK_REQ) { + err = -ENOSPC; + goto msg_err; + } + + /* Notify FW if the data link has been severed */ + if (!cmpl_data->fw_update.data) { + err = -ECANCELED; + goto msg_err; + } + + /* Pull length/offset pair and mark it as complete */ + get_unsigned_result(FBNIC_FW_WRITE_CHUNK_OFFSET, offset); + get_unsigned_result(FBNIC_FW_WRITE_CHUNK_LENGTH, length); + + /* Record offset and length for the response */ + if (offset) { + err = fbnic_tlv_attr_put_int(msg, FBNIC_FW_WRITE_CHUNK_OFFSET, + offset); + if (err) + goto msg_err; + } + + err = fbnic_tlv_attr_put_int(msg, FBNIC_FW_WRITE_CHUNK_LENGTH, + length); + if (err) + goto msg_err; + + /* Verify length */ + if (!length || length > TLV_MAX_DATA) { + err = -EINVAL; + goto msg_err; + } + + /* Verify offset and length are within bounds */ + if (offset >= cmpl_data->fw_update.size || + offset + length > cmpl_data->fw_update.size) { + err = -EFAULT; + goto msg_err; + } + + /* Add outbound data to message */ + err = fbnic_tlv_attr_put_value(msg, FBNIC_FW_WRITE_CHUNK_DATA, + cmpl_data->fw_update.data + offset, + length); + + /* Notify the waiting thread that we processed a message */ + if (!err) + cmpl_data->fw_update.last_offset = offset; + + cmpl_data->result = err; + complete(&cmpl_data->done); + +msg_err: + /* Report error to FW if one occurred */ + if (err) + fbnic_tlv_attr_put_int(msg, FBNIC_FW_WRITE_CHUNK_ERROR, err); + + /* Map and send the response */ + err = fbnic_mbx_map_tlv_msg(fbd, msg); + if (err) + free_page((unsigned long)msg); + + return err; +} + +static const struct fbnic_tlv_index fbnic_fw_verify_image_resp_index[] = { + FBNIC_TLV_ATTR_S32(FBNIC_FW_VERIFY_IMAGE_ERROR), + FBNIC_TLV_ATTR_LAST +}; + +static int fbnic_fw_parse_fw_verify_image_resp(void *opaque, + struct fbnic_tlv_msg **results) +{ + struct fbnic_fw_completion *cmpl_data; + struct fbnic_dev *fbd = opaque; + int err = 0; + + /* Verify we have a completion pointer */ + cmpl_data = READ_ONCE(fbd->cmpl_data); + if (!cmpl_data || + cmpl_data->msg_type != FBNIC_TLV_MSG_ID_FW_VERIFY_IMAGE_RESP) + return -ENOSPC; + + /* Check for errors */ + get_signed_result(FBNIC_FW_VERIFY_IMAGE_ERROR, err); + + cmpl_data->result = err; + complete(&cmpl_data->done); + + return err; +} + +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; + int err = 0; + + /* Verify we have a completion pointer */ + cmpl_data = READ_ONCE(fbd->cmpl_data); + if (!cmpl_data || + cmpl_data->msg_type != FBNIC_TLV_MSG_ID_FW_WRITE_CHUNK_REQ) + return -ENOSPC; + + /* Check for errors */ + get_signed_result(FBNIC_FW_FINISH_UPGRADE_ERROR, err); + + /* Close out update by clearing data pointer */ + cmpl_data->fw_update.last_offset = cmpl_data->fw_update.size; + cmpl_data->fw_update.data = NULL; + + cmpl_data->result = err; + complete(&cmpl_data->done); + + return 0; +} + static const struct fbnic_tlv_parser fbnic_fw_tlv_parser[] = { FBNIC_TLV_PARSER(FW_CAP_RESP, fbnic_fw_cap_resp_index, fbnic_fw_parse_cap_resp), @@ -658,6 +909,18 @@ 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_VERIFY_IMAGE_RESP, + fbnic_fw_verify_image_resp_index, + fbnic_fw_parse_fw_verify_image_resp), + FBNIC_TLV_PARSER(FW_FINISH_UPGRADE_REQ, + fbnic_fw_finish_upgrade_req_index, + fbnic_fw_parse_fw_finish_upgrade_req), FBNIC_TLV_MSG_ERROR }; diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_fw.h b/drivers/net/ethernet/meta/fbnic/fbnic_fw.h index 7cd8841920e4..347e60fee551 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_fw.h +++ b/drivers/net/ethernet/meta/fbnic/fbnic_fw.h @@ -45,10 +45,20 @@ struct fbnic_fw_cap { }; struct fbnic_fw_completion { - struct { - s32 millivolts; - s32 millidegrees; - } tsene; + u32 msg_type; + struct completion done; + int result; + union { + struct { + u32 size; + u32 last_offset; + const u8 *data; + } fw_update; + struct { + s32 millivolts; + s32 millidegrees; + } tsene; + }; }; void fbnic_mbx_init(struct fbnic_dev *fbd); @@ -59,6 +69,9 @@ void fbnic_mbx_flush_tx(struct fbnic_dev *fbd); int fbnic_fw_xmit_ownership_msg(struct fbnic_dev *fbd, bool take_ownership); int fbnic_fw_init_heartbeat(struct fbnic_dev *fbd, bool poll); void fbnic_fw_check_heartbeat(struct fbnic_dev *fbd); +int fbnic_fw_xmit_fw_start_upgrade(struct fbnic_dev *fbd, + struct fbnic_fw_completion *cmpl_data, + unsigned int id, unsigned int len); #define fbnic_mk_full_fw_ver_str(_rev_id, _delim, _commit, _str, _str_sz) \ do { \ @@ -74,6 +87,15 @@ do { \ #define fbnic_mk_fw_ver_str(_rev_id, _str) \ fbnic_mk_full_fw_ver_str(_rev_id, "", "", _str, sizeof(_str)) +enum { + QSPI_SECTION_CMRT = 0, + QSPI_SECTION_CONTROL_FW = 1, + QSPI_SECTION_UCODE = 2, + QSPI_SECTION_OPTION_ROM = 3, + QSPI_SECTION_USER = 4, + QSPI_SECTION_INVALID, +}; + #define FW_HEARTBEAT_PERIOD (10 * HZ) enum { @@ -83,6 +105,14 @@ enum { FBNIC_TLV_MSG_ID_OWNERSHIP_RESP = 0x13, FBNIC_TLV_MSG_ID_HEARTBEAT_REQ = 0x14, FBNIC_TLV_MSG_ID_HEARTBEAT_RESP = 0x15, + FBNIC_TLV_MSG_ID_FW_START_UPGRADE_REQ = 0x22, + FBNIC_TLV_MSG_ID_FW_START_UPGRADE_RESP = 0x23, + FBNIC_TLV_MSG_ID_FW_WRITE_CHUNK_REQ = 0x24, + FBNIC_TLV_MSG_ID_FW_WRITE_CHUNK_RESP = 0x25, + FBNIC_TLV_MSG_ID_FW_VERIFY_IMAGE_REQ = 0x26, + FBNIC_TLV_MSG_ID_FW_VERIFY_IMAGE_RESP = 0x27, + FBNIC_TLV_MSG_ID_FW_FINISH_UPGRADE_REQ = 0x28, + FBNIC_TLV_MSG_ID_FW_FINISH_UPGRADE_RESP = 0x29, }; #define FBNIC_FW_CAP_RESP_VERSION_MAJOR CSR_GENMASK(31, 24) @@ -128,4 +158,30 @@ enum { FBNIC_FW_OWNERSHIP_FLAG = 0x0, FBNIC_FW_OWNERSHIP_MSG_MAX }; + +enum { + FBNIC_FW_START_UPGRADE_ERROR = 0x0, + FBNIC_FW_START_UPGRADE_SECTION = 0x1, + FBNIC_FW_START_UPGRADE_IMAGE_LENGTH = 0x2, + FBNIC_FW_START_UPGRADE_MSG_MAX +}; + +enum { + FBNIC_FW_WRITE_CHUNK_OFFSET = 0x0, + FBNIC_FW_WRITE_CHUNK_LENGTH = 0x1, + FBNIC_FW_WRITE_CHUNK_DATA = 0x2, + FBNIC_FW_WRITE_CHUNK_ERROR = 0x3, + FBNIC_FW_WRITE_CHUNK_MSG_MAX +}; + +enum { + FBNIC_FW_VERIFY_IMAGE_ERROR = 0x0, + FBNIC_FW_VERIFY_IMAGE_MSG_MAX +}; + +enum { + FBNIC_FW_FINISH_UPGRADE_ERROR = 0x0, + FBNIC_FW_FINISH_UPGRADE_MSG_MAX +}; + #endif /* _FBNIC_FW_H_ */ -- 2.43.5