2024-06-27 16:08:55 +02:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-only
|
|
|
|
|
|
|
|
#include <linux/ethtool.h>
|
|
|
|
#include <linux/firmware.h>
|
|
|
|
|
|
|
|
#include "common.h"
|
|
|
|
#include "module_fw.h"
|
|
|
|
#include "cmis.h"
|
|
|
|
|
|
|
|
struct cmis_fw_update_fw_mng_features {
|
|
|
|
u8 start_cmd_payload_size;
|
|
|
|
u16 max_duration_start;
|
|
|
|
u16 max_duration_write;
|
|
|
|
u16 max_duration_complete;
|
|
|
|
};
|
|
|
|
|
|
|
|
/* See section 9.4.2 "CMD 0041h: Firmware Management Features" in CMIS standard
|
|
|
|
* revision 5.2.
|
|
|
|
* struct cmis_cdb_fw_mng_features_rpl is a structured layout of the flat
|
|
|
|
* array, ethtool_cmis_cdb_rpl::payload.
|
|
|
|
*/
|
|
|
|
struct cmis_cdb_fw_mng_features_rpl {
|
|
|
|
u8 resv1;
|
|
|
|
u8 resv2;
|
|
|
|
u8 start_cmd_payload_size;
|
|
|
|
u8 resv3;
|
|
|
|
u8 read_write_len_ext;
|
|
|
|
u8 write_mechanism;
|
|
|
|
u8 resv4;
|
|
|
|
u8 resv5;
|
|
|
|
__be16 max_duration_start;
|
|
|
|
__be16 resv6;
|
|
|
|
__be16 max_duration_write;
|
|
|
|
__be16 max_duration_complete;
|
|
|
|
__be16 resv7;
|
|
|
|
};
|
|
|
|
|
2024-08-12 16:08:24 +02:00
|
|
|
enum cmis_cdb_fw_write_mechanism {
|
|
|
|
CMIS_CDB_FW_WRITE_MECHANISM_LPL = 0x01,
|
|
|
|
CMIS_CDB_FW_WRITE_MECHANISM_BOTH = 0x11,
|
|
|
|
};
|
2024-06-27 16:08:55 +02:00
|
|
|
|
|
|
|
static int
|
|
|
|
cmis_fw_update_fw_mng_features_get(struct ethtool_cmis_cdb *cdb,
|
|
|
|
struct net_device *dev,
|
|
|
|
struct cmis_fw_update_fw_mng_features *fw_mng,
|
|
|
|
struct ethnl_module_fw_flash_ntf_params *ntf_params)
|
|
|
|
{
|
|
|
|
struct ethtool_cmis_cdb_cmd_args args = {};
|
|
|
|
struct cmis_cdb_fw_mng_features_rpl *rpl;
|
|
|
|
u8 flags = CDB_F_STATUS_VALID;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
ethtool_cmis_cdb_check_completion_flag(cdb->cmis_rev, &flags);
|
|
|
|
ethtool_cmis_cdb_compose_args(&args,
|
|
|
|
ETHTOOL_CMIS_CDB_CMD_FW_MANAGMENT_FEATURES,
|
|
|
|
NULL, 0, cdb->max_completion_time,
|
|
|
|
cdb->read_write_len_ext, 1000,
|
|
|
|
sizeof(*rpl), flags);
|
|
|
|
|
|
|
|
err = ethtool_cmis_cdb_execute_cmd(dev, &args);
|
|
|
|
if (err < 0) {
|
|
|
|
ethnl_module_fw_flash_ntf_err(dev, ntf_params,
|
|
|
|
"FW Management Features command failed",
|
|
|
|
args.err_msg);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
rpl = (struct cmis_cdb_fw_mng_features_rpl *)args.req.payload;
|
2024-08-12 16:08:24 +02:00
|
|
|
if (!(rpl->write_mechanism == CMIS_CDB_FW_WRITE_MECHANISM_LPL ||
|
|
|
|
rpl->write_mechanism == CMIS_CDB_FW_WRITE_MECHANISM_BOTH)) {
|
2024-06-27 16:08:55 +02:00
|
|
|
ethnl_module_fw_flash_ntf_err(dev, ntf_params,
|
|
|
|
"Write LPL is not supported",
|
|
|
|
NULL);
|
|
|
|
return -EOPNOTSUPP;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Above, we used read_write_len_ext that we got from CDB
|
|
|
|
* advertisement. Update it with the value that we got from module
|
|
|
|
* features query, which is specific for Firmware Management Commands
|
|
|
|
* (IDs 0100h-01FFh).
|
|
|
|
*/
|
|
|
|
cdb->read_write_len_ext = rpl->read_write_len_ext;
|
|
|
|
fw_mng->start_cmd_payload_size = rpl->start_cmd_payload_size;
|
|
|
|
fw_mng->max_duration_start = be16_to_cpu(rpl->max_duration_start);
|
|
|
|
fw_mng->max_duration_write = be16_to_cpu(rpl->max_duration_write);
|
|
|
|
fw_mng->max_duration_complete = be16_to_cpu(rpl->max_duration_complete);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* See section 9.7.2 "CMD 0101h: Start Firmware Download" in CMIS standard
|
|
|
|
* revision 5.2.
|
|
|
|
* struct cmis_cdb_start_fw_download_pl is a structured layout of the
|
|
|
|
* flat array, ethtool_cmis_cdb_request::payload.
|
|
|
|
*/
|
|
|
|
struct cmis_cdb_start_fw_download_pl {
|
|
|
|
__struct_group(cmis_cdb_start_fw_download_pl_h, head, /* no attrs */,
|
|
|
|
__be32 image_size;
|
|
|
|
__be32 resv1;
|
|
|
|
);
|
|
|
|
u8 vendor_data[ETHTOOL_CMIS_CDB_LPL_MAX_PL_LENGTH -
|
|
|
|
sizeof(struct cmis_cdb_start_fw_download_pl_h)];
|
|
|
|
};
|
|
|
|
|
|
|
|
static int
|
|
|
|
cmis_fw_update_start_download(struct ethtool_cmis_cdb *cdb,
|
|
|
|
struct ethtool_cmis_fw_update_params *fw_update,
|
|
|
|
struct cmis_fw_update_fw_mng_features *fw_mng)
|
|
|
|
{
|
|
|
|
u8 vendor_data_size = fw_mng->start_cmd_payload_size;
|
|
|
|
struct cmis_cdb_start_fw_download_pl pl = {};
|
|
|
|
struct ethtool_cmis_cdb_cmd_args args = {};
|
|
|
|
u8 lpl_len;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
pl.image_size = cpu_to_be32(fw_update->fw->size);
|
|
|
|
memcpy(pl.vendor_data, fw_update->fw->data, vendor_data_size);
|
|
|
|
|
|
|
|
lpl_len = offsetof(struct cmis_cdb_start_fw_download_pl,
|
|
|
|
vendor_data[vendor_data_size]);
|
|
|
|
|
|
|
|
ethtool_cmis_cdb_compose_args(&args,
|
|
|
|
ETHTOOL_CMIS_CDB_CMD_START_FW_DOWNLOAD,
|
|
|
|
(u8 *)&pl, lpl_len,
|
|
|
|
fw_mng->max_duration_start,
|
|
|
|
cdb->read_write_len_ext, 1000, 0,
|
|
|
|
CDB_F_COMPLETION_VALID | CDB_F_STATUS_VALID);
|
|
|
|
|
|
|
|
err = ethtool_cmis_cdb_execute_cmd(fw_update->dev, &args);
|
|
|
|
if (err < 0)
|
|
|
|
ethnl_module_fw_flash_ntf_err(fw_update->dev,
|
|
|
|
&fw_update->ntf_params,
|
|
|
|
"Start FW download command failed",
|
|
|
|
args.err_msg);
|
|
|
|
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* See section 9.7.4 "CMD 0103h: Write Firmware Block LPL" in CMIS standard
|
|
|
|
* revision 5.2.
|
|
|
|
* struct cmis_cdb_write_fw_block_lpl_pl is a structured layout of the
|
|
|
|
* flat array, ethtool_cmis_cdb_request::payload.
|
|
|
|
*/
|
|
|
|
struct cmis_cdb_write_fw_block_lpl_pl {
|
|
|
|
__be32 block_address;
|
|
|
|
u8 fw_block[ETHTOOL_CMIS_CDB_LPL_MAX_PL_LENGTH - sizeof(__be32)];
|
|
|
|
};
|
|
|
|
|
|
|
|
static int
|
|
|
|
cmis_fw_update_write_image(struct ethtool_cmis_cdb *cdb,
|
|
|
|
struct ethtool_cmis_fw_update_params *fw_update,
|
|
|
|
struct cmis_fw_update_fw_mng_features *fw_mng)
|
|
|
|
{
|
|
|
|
u8 start = fw_mng->start_cmd_payload_size;
|
|
|
|
u32 offset, max_block_size, max_lpl_len;
|
|
|
|
u32 image_size = fw_update->fw->size;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
max_lpl_len = min_t(u32,
|
|
|
|
ethtool_cmis_get_max_payload_size(cdb->read_write_len_ext),
|
|
|
|
ETHTOOL_CMIS_CDB_LPL_MAX_PL_LENGTH);
|
|
|
|
max_block_size =
|
|
|
|
max_lpl_len - sizeof_field(struct cmis_cdb_write_fw_block_lpl_pl,
|
|
|
|
block_address);
|
|
|
|
|
|
|
|
for (offset = start; offset < image_size; offset += max_block_size) {
|
|
|
|
struct cmis_cdb_write_fw_block_lpl_pl pl = {
|
|
|
|
.block_address = cpu_to_be32(offset - start),
|
|
|
|
};
|
|
|
|
struct ethtool_cmis_cdb_cmd_args args = {};
|
|
|
|
u32 block_size, lpl_len;
|
|
|
|
|
|
|
|
ethnl_module_fw_flash_ntf_in_progress(fw_update->dev,
|
|
|
|
&fw_update->ntf_params,
|
|
|
|
offset - start,
|
|
|
|
image_size);
|
|
|
|
block_size = min_t(u32, max_block_size, image_size - offset);
|
|
|
|
memcpy(pl.fw_block, &fw_update->fw->data[offset], block_size);
|
|
|
|
lpl_len = block_size +
|
|
|
|
sizeof_field(struct cmis_cdb_write_fw_block_lpl_pl,
|
|
|
|
block_address);
|
|
|
|
|
|
|
|
ethtool_cmis_cdb_compose_args(&args,
|
|
|
|
ETHTOOL_CMIS_CDB_CMD_WRITE_FW_BLOCK_LPL,
|
|
|
|
(u8 *)&pl, lpl_len,
|
|
|
|
fw_mng->max_duration_write,
|
|
|
|
cdb->read_write_len_ext, 1, 0,
|
|
|
|
CDB_F_COMPLETION_VALID | CDB_F_STATUS_VALID);
|
|
|
|
|
|
|
|
err = ethtool_cmis_cdb_execute_cmd(fw_update->dev, &args);
|
|
|
|
if (err < 0) {
|
|
|
|
ethnl_module_fw_flash_ntf_err(fw_update->dev,
|
|
|
|
&fw_update->ntf_params,
|
|
|
|
"Write FW block LPL command failed",
|
|
|
|
args.err_msg);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
cmis_fw_update_complete_download(struct ethtool_cmis_cdb *cdb,
|
|
|
|
struct net_device *dev,
|
|
|
|
struct cmis_fw_update_fw_mng_features *fw_mng,
|
|
|
|
struct ethnl_module_fw_flash_ntf_params *ntf_params)
|
|
|
|
{
|
|
|
|
struct ethtool_cmis_cdb_cmd_args args = {};
|
|
|
|
int err;
|
|
|
|
|
|
|
|
ethtool_cmis_cdb_compose_args(&args,
|
|
|
|
ETHTOOL_CMIS_CDB_CMD_COMPLETE_FW_DOWNLOAD,
|
|
|
|
NULL, 0, fw_mng->max_duration_complete,
|
|
|
|
cdb->read_write_len_ext, 1000, 0,
|
|
|
|
CDB_F_COMPLETION_VALID | CDB_F_STATUS_VALID);
|
|
|
|
|
|
|
|
err = ethtool_cmis_cdb_execute_cmd(dev, &args);
|
|
|
|
if (err < 0)
|
|
|
|
ethnl_module_fw_flash_ntf_err(dev, ntf_params,
|
|
|
|
"Complete FW download command failed",
|
|
|
|
args.err_msg);
|
|
|
|
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
cmis_fw_update_download_image(struct ethtool_cmis_cdb *cdb,
|
|
|
|
struct ethtool_cmis_fw_update_params *fw_update,
|
|
|
|
struct cmis_fw_update_fw_mng_features *fw_mng)
|
|
|
|
{
|
|
|
|
int err;
|
|
|
|
|
|
|
|
err = cmis_fw_update_start_download(cdb, fw_update, fw_mng);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
err = cmis_fw_update_write_image(cdb, fw_update, fw_mng);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
err = cmis_fw_update_complete_download(cdb, fw_update->dev, fw_mng,
|
|
|
|
&fw_update->ntf_params);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
enum {
|
|
|
|
CMIS_MODULE_LOW_PWR = 1,
|
|
|
|
CMIS_MODULE_READY = 3,
|
|
|
|
};
|
|
|
|
|
|
|
|
static bool module_is_ready(u8 data)
|
|
|
|
{
|
|
|
|
u8 state = (data >> 1) & 7;
|
|
|
|
|
|
|
|
return state == CMIS_MODULE_READY || state == CMIS_MODULE_LOW_PWR;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define CMIS_MODULE_READY_MAX_DURATION_MSEC 1000
|
|
|
|
#define CMIS_MODULE_STATE_OFFSET 3
|
|
|
|
|
|
|
|
static int
|
|
|
|
cmis_fw_update_wait_for_module_state(struct net_device *dev, u8 flags)
|
|
|
|
{
|
|
|
|
u8 state;
|
|
|
|
|
|
|
|
return ethtool_cmis_wait_for_cond(dev, flags, CDB_F_MODULE_STATE_VALID,
|
|
|
|
CMIS_MODULE_READY_MAX_DURATION_MSEC,
|
|
|
|
CMIS_MODULE_STATE_OFFSET,
|
|
|
|
module_is_ready, NULL, &state);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* See section 9.7.10 "CMD 0109h: Run Firmware Image" in CMIS standard
|
|
|
|
* revision 5.2.
|
|
|
|
* struct cmis_cdb_run_fw_image_pl is a structured layout of the flat
|
|
|
|
* array, ethtool_cmis_cdb_request::payload.
|
|
|
|
*/
|
|
|
|
struct cmis_cdb_run_fw_image_pl {
|
|
|
|
u8 resv1;
|
|
|
|
u8 image_to_run;
|
|
|
|
u16 delay_to_reset;
|
|
|
|
};
|
|
|
|
|
|
|
|
static int
|
|
|
|
cmis_fw_update_run_image(struct ethtool_cmis_cdb *cdb, struct net_device *dev,
|
|
|
|
struct ethnl_module_fw_flash_ntf_params *ntf_params)
|
|
|
|
{
|
|
|
|
struct ethtool_cmis_cdb_cmd_args args = {};
|
|
|
|
struct cmis_cdb_run_fw_image_pl pl = {0};
|
|
|
|
int err;
|
|
|
|
|
|
|
|
ethtool_cmis_cdb_compose_args(&args, ETHTOOL_CMIS_CDB_CMD_RUN_FW_IMAGE,
|
|
|
|
(u8 *)&pl, sizeof(pl),
|
|
|
|
cdb->max_completion_time,
|
|
|
|
cdb->read_write_len_ext, 1000, 0,
|
|
|
|
CDB_F_MODULE_STATE_VALID);
|
|
|
|
|
|
|
|
err = ethtool_cmis_cdb_execute_cmd(dev, &args);
|
|
|
|
if (err < 0) {
|
|
|
|
ethnl_module_fw_flash_ntf_err(dev, ntf_params,
|
|
|
|
"Run image command failed",
|
|
|
|
args.err_msg);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
err = cmis_fw_update_wait_for_module_state(dev, args.flags);
|
|
|
|
if (err < 0)
|
|
|
|
ethnl_module_fw_flash_ntf_err(dev, ntf_params,
|
|
|
|
"Module is not ready on time after reset",
|
|
|
|
NULL);
|
|
|
|
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
cmis_fw_update_commit_image(struct ethtool_cmis_cdb *cdb,
|
|
|
|
struct net_device *dev,
|
|
|
|
struct ethnl_module_fw_flash_ntf_params *ntf_params)
|
|
|
|
{
|
|
|
|
struct ethtool_cmis_cdb_cmd_args args = {};
|
|
|
|
int err;
|
|
|
|
|
|
|
|
ethtool_cmis_cdb_compose_args(&args,
|
|
|
|
ETHTOOL_CMIS_CDB_CMD_COMMIT_FW_IMAGE,
|
|
|
|
NULL, 0, cdb->max_completion_time,
|
|
|
|
cdb->read_write_len_ext, 1000, 0,
|
|
|
|
CDB_F_COMPLETION_VALID | CDB_F_STATUS_VALID);
|
|
|
|
|
|
|
|
err = ethtool_cmis_cdb_execute_cmd(dev, &args);
|
|
|
|
if (err < 0)
|
|
|
|
ethnl_module_fw_flash_ntf_err(dev, ntf_params,
|
|
|
|
"Commit image command failed",
|
|
|
|
args.err_msg);
|
|
|
|
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int cmis_fw_update_reset(struct net_device *dev)
|
|
|
|
{
|
|
|
|
__u32 reset_data = ETH_RESET_PHY;
|
|
|
|
|
|
|
|
return dev->ethtool_ops->reset(dev, &reset_data);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
ethtool_cmis_fw_update(struct ethtool_cmis_fw_update_params *fw_update)
|
|
|
|
{
|
|
|
|
struct ethnl_module_fw_flash_ntf_params *ntf_params =
|
|
|
|
&fw_update->ntf_params;
|
|
|
|
struct cmis_fw_update_fw_mng_features fw_mng = {0};
|
|
|
|
struct net_device *dev = fw_update->dev;
|
|
|
|
struct ethtool_cmis_cdb *cdb;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
cdb = ethtool_cmis_cdb_init(dev, &fw_update->params, ntf_params);
|
|
|
|
if (IS_ERR(cdb))
|
|
|
|
goto err_send_ntf;
|
|
|
|
|
|
|
|
ethnl_module_fw_flash_ntf_start(dev, ntf_params);
|
|
|
|
|
|
|
|
err = cmis_fw_update_fw_mng_features_get(cdb, dev, &fw_mng, ntf_params);
|
|
|
|
if (err < 0)
|
|
|
|
goto err_cdb_fini;
|
|
|
|
|
|
|
|
err = cmis_fw_update_download_image(cdb, fw_update, &fw_mng);
|
|
|
|
if (err < 0)
|
|
|
|
goto err_cdb_fini;
|
|
|
|
|
|
|
|
err = cmis_fw_update_run_image(cdb, dev, ntf_params);
|
|
|
|
if (err < 0)
|
|
|
|
goto err_cdb_fini;
|
|
|
|
|
|
|
|
/* The CDB command "Run Firmware Image" resets the firmware, so the new
|
|
|
|
* one might have different settings.
|
|
|
|
* Free the old CDB instance, and init a new one.
|
|
|
|
*/
|
|
|
|
ethtool_cmis_cdb_fini(cdb);
|
|
|
|
|
|
|
|
cdb = ethtool_cmis_cdb_init(dev, &fw_update->params, ntf_params);
|
|
|
|
if (IS_ERR(cdb))
|
|
|
|
goto err_send_ntf;
|
|
|
|
|
|
|
|
err = cmis_fw_update_commit_image(cdb, dev, ntf_params);
|
|
|
|
if (err < 0)
|
|
|
|
goto err_cdb_fini;
|
|
|
|
|
|
|
|
err = cmis_fw_update_reset(dev);
|
|
|
|
if (err < 0)
|
|
|
|
goto err_cdb_fini;
|
|
|
|
|
|
|
|
ethnl_module_fw_flash_ntf_complete(dev, ntf_params);
|
|
|
|
ethtool_cmis_cdb_fini(cdb);
|
|
|
|
return;
|
|
|
|
|
|
|
|
err_cdb_fini:
|
|
|
|
ethtool_cmis_cdb_fini(cdb);
|
|
|
|
err_send_ntf:
|
|
|
|
ethnl_module_fw_flash_ntf_err(dev, ntf_params, NULL, NULL);
|
|
|
|
}
|