forked from mirrors/linux
		
	net: ethtool: Add support for writing firmware blocks using EPL payload
In the CMIS specification for pluggable modules, LPL (Local Payload) and EPL (Extended Payload) are two types of data payloads used for managing various functions and features of the module. EPL payloads are used for more complex and extensive management functions that require a larger amount of data, so writing firmware blocks using EPL is much more efficient. Currently, only LPL payload is supported for writing firmware blocks to the module. Add support for writing firmware block using EPL payload, both to support modules that supports only EPL write mechanism, and to optimize the flashing process of modules that support LPL and EPL. Signed-off-by: Danielle Ratson <danieller@nvidia.com> Reviewed-by: Petr Machata <petrm@nvidia.com> Reviewed-by: Simon Horman <horms@kernel.org> Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
		
							parent
							
								
									edc3445689
								
							
						
					
					
						commit
						9a3b0d078b
					
				
					 3 changed files with 148 additions and 13 deletions
				
			
		|  | @ -1,6 +1,7 @@ | |||
| /* SPDX-License-Identifier: GPL-2.0-only */ | ||||
| 
 | ||||
| #define ETHTOOL_CMIS_CDB_LPL_MAX_PL_LENGTH		120 | ||||
| #define ETHTOOL_CMIS_CDB_EPL_MAX_PL_LENGTH		2048 | ||||
| #define ETHTOOL_CMIS_CDB_CMD_PAGE			0x9F | ||||
| #define ETHTOOL_CMIS_CDB_PAGE_I2C_ADDR			0x50 | ||||
| 
 | ||||
|  | @ -23,6 +24,7 @@ enum ethtool_cmis_cdb_cmd_id { | |||
| 	ETHTOOL_CMIS_CDB_CMD_FW_MANAGMENT_FEATURES	= 0x0041, | ||||
| 	ETHTOOL_CMIS_CDB_CMD_START_FW_DOWNLOAD		= 0x0101, | ||||
| 	ETHTOOL_CMIS_CDB_CMD_WRITE_FW_BLOCK_LPL		= 0x0103, | ||||
| 	ETHTOOL_CMIS_CDB_CMD_WRITE_FW_BLOCK_EPL		= 0x0104, | ||||
| 	ETHTOOL_CMIS_CDB_CMD_COMPLETE_FW_DOWNLOAD	= 0x0107, | ||||
| 	ETHTOOL_CMIS_CDB_CMD_RUN_FW_IMAGE		= 0x0109, | ||||
| 	ETHTOOL_CMIS_CDB_CMD_COMMIT_FW_IMAGE		= 0x010A, | ||||
|  | @ -38,6 +40,7 @@ enum ethtool_cmis_cdb_cmd_id { | |||
|  * @resv1: Added to match the CMIS standard request continuity. | ||||
|  * @resv2: Added to match the CMIS standard request continuity. | ||||
|  * @payload: Payload for the CDB commands. | ||||
|  * @epl: Extended payload for the CDB commands. | ||||
|  */ | ||||
| struct ethtool_cmis_cdb_request { | ||||
| 	__be16 id; | ||||
|  | @ -49,6 +52,7 @@ struct ethtool_cmis_cdb_request { | |||
| 		u8 resv2; | ||||
| 		u8 payload[ETHTOOL_CMIS_CDB_LPL_MAX_PL_LENGTH]; | ||||
| 	); | ||||
| 	u8 *epl;	/* Everything above this field checksummed. */ | ||||
| }; | ||||
| 
 | ||||
| #define CDB_F_COMPLETION_VALID		BIT(0) | ||||
|  |  | |||
|  | @ -33,12 +33,19 @@ void ethtool_cmis_cdb_compose_args(struct ethtool_cmis_cdb_cmd_args *args, | |||
| { | ||||
| 	args->req.id = cpu_to_be16(cmd); | ||||
| 	args->req.lpl_len = lpl_len; | ||||
| 	if (lpl) | ||||
| 	if (lpl) { | ||||
| 		memcpy(args->req.payload, lpl, args->req.lpl_len); | ||||
| 
 | ||||
| 	args->max_duration = max_duration; | ||||
| 		args->read_write_len_ext = | ||||
| 			ethtool_cmis_get_max_lpl_size(read_write_len_ext); | ||||
| 	} | ||||
| 	if (epl) { | ||||
| 		args->req.epl_len = cpu_to_be16(epl_len); | ||||
| 		args->req.epl = epl; | ||||
| 		args->read_write_len_ext = | ||||
| 			ethtool_cmis_get_max_epl_size(read_write_len_ext); | ||||
| 	} | ||||
| 
 | ||||
| 	args->max_duration = max_duration; | ||||
| 	args->msleep_pre_rpl = msleep_pre_rpl; | ||||
| 	args->rpl_exp_len = rpl_exp_len; | ||||
| 	args->flags = flags; | ||||
|  | @ -556,6 +563,49 @@ __ethtool_cmis_cdb_execute_cmd(struct net_device *dev, | |||
| 	return err; | ||||
| } | ||||
| 
 | ||||
| #define CMIS_CDB_EPL_PAGE_START			0xA0 | ||||
| #define CMIS_CDB_EPL_PAGE_END			0xAF | ||||
| #define CMIS_CDB_EPL_FW_BLOCK_OFFSET_START	128 | ||||
| #define CMIS_CDB_EPL_FW_BLOCK_OFFSET_END	255 | ||||
| 
 | ||||
| static int | ||||
| ethtool_cmis_cdb_execute_epl_cmd(struct net_device *dev, | ||||
| 				 struct ethtool_cmis_cdb_cmd_args *args, | ||||
| 				 struct ethtool_module_eeprom *page_data) | ||||
| { | ||||
| 	u16 epl_len = be16_to_cpu(args->req.epl_len); | ||||
| 	u32 bytes_written = 0; | ||||
| 	u8 page; | ||||
| 	int err; | ||||
| 
 | ||||
| 	for (page = CMIS_CDB_EPL_PAGE_START; | ||||
| 	     page <= CMIS_CDB_EPL_PAGE_END && bytes_written < epl_len; page++) { | ||||
| 		u16 offset = CMIS_CDB_EPL_FW_BLOCK_OFFSET_START; | ||||
| 
 | ||||
| 		while (offset <= CMIS_CDB_EPL_FW_BLOCK_OFFSET_END && | ||||
| 		       bytes_written < epl_len) { | ||||
| 			u32 bytes_left = epl_len - bytes_written; | ||||
| 			u16 space_left, bytes_to_write; | ||||
| 
 | ||||
| 			space_left = CMIS_CDB_EPL_FW_BLOCK_OFFSET_END - offset + 1; | ||||
| 			bytes_to_write = min_t(u16, bytes_left, | ||||
| 					       min_t(u16, space_left, | ||||
| 						     args->read_write_len_ext)); | ||||
| 
 | ||||
| 			err = __ethtool_cmis_cdb_execute_cmd(dev, page_data, | ||||
| 							     page, offset, | ||||
| 							     bytes_to_write, | ||||
| 							     args->req.epl + bytes_written); | ||||
| 			if (err < 0) | ||||
| 				return err; | ||||
| 
 | ||||
| 			offset += bytes_to_write; | ||||
| 			bytes_written += bytes_to_write; | ||||
| 		} | ||||
| 	} | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static u8 cmis_cdb_calc_checksum(const void *data, size_t size) | ||||
| { | ||||
| 	const u8 *bytes = (const u8 *)data; | ||||
|  | @ -577,7 +627,9 @@ int ethtool_cmis_cdb_execute_cmd(struct net_device *dev, | |||
| 	int err; | ||||
| 
 | ||||
| 	args->req.chk_code = | ||||
| 		cmis_cdb_calc_checksum(&args->req, sizeof(args->req)); | ||||
| 		cmis_cdb_calc_checksum(&args->req, | ||||
| 				       offsetof(struct ethtool_cmis_cdb_request, | ||||
| 						epl)); | ||||
| 
 | ||||
| 	if (args->req.lpl_len > args->read_write_len_ext) { | ||||
| 		args->err_msg = "LPL length is longer than CDB read write length extension allows"; | ||||
|  | @ -599,6 +651,12 @@ int ethtool_cmis_cdb_execute_cmd(struct net_device *dev, | |||
| 	if (err < 0) | ||||
| 		return err; | ||||
| 
 | ||||
| 	if (args->req.epl_len) { | ||||
| 		err = ethtool_cmis_cdb_execute_epl_cmd(dev, args, &page_data); | ||||
| 		if (err < 0) | ||||
| 			return err; | ||||
| 	} | ||||
| 
 | ||||
| 	offset = CMIS_CDB_CMD_ID_OFFSET + | ||||
| 		offsetof(struct ethtool_cmis_cdb_request, id); | ||||
| 	err = __ethtool_cmis_cdb_execute_cmd(dev, &page_data, | ||||
|  |  | |||
|  | @ -9,6 +9,7 @@ | |||
| 
 | ||||
| struct cmis_fw_update_fw_mng_features { | ||||
| 	u8	start_cmd_payload_size; | ||||
| 	u8	write_mechanism; | ||||
| 	u16	max_duration_start; | ||||
| 	u16	max_duration_write; | ||||
| 	u16	max_duration_complete; | ||||
|  | @ -36,7 +37,9 @@ struct cmis_cdb_fw_mng_features_rpl { | |||
| }; | ||||
| 
 | ||||
| enum cmis_cdb_fw_write_mechanism { | ||||
| 	CMIS_CDB_FW_WRITE_MECHANISM_NONE	= 0x00, | ||||
| 	CMIS_CDB_FW_WRITE_MECHANISM_LPL		= 0x01, | ||||
| 	CMIS_CDB_FW_WRITE_MECHANISM_EPL		= 0x10, | ||||
| 	CMIS_CDB_FW_WRITE_MECHANISM_BOTH	= 0x11, | ||||
| }; | ||||
| 
 | ||||
|  | @ -68,10 +71,9 @@ cmis_fw_update_fw_mng_features_get(struct ethtool_cmis_cdb *cdb, | |||
| 	} | ||||
| 
 | ||||
| 	rpl = (struct cmis_cdb_fw_mng_features_rpl *)args.req.payload; | ||||
| 	if (!(rpl->write_mechanism == CMIS_CDB_FW_WRITE_MECHANISM_LPL || | ||||
| 	      rpl->write_mechanism == CMIS_CDB_FW_WRITE_MECHANISM_BOTH)) { | ||||
| 	if (rpl->write_mechanism == CMIS_CDB_FW_WRITE_MECHANISM_NONE) { | ||||
| 		ethnl_module_fw_flash_ntf_err(dev, ntf_params, | ||||
| 					      "Write LPL is not supported", | ||||
| 					      "CDB write mechanism is not supported", | ||||
| 					      NULL); | ||||
| 		return  -EOPNOTSUPP; | ||||
| 	} | ||||
|  | @ -83,6 +85,10 @@ cmis_fw_update_fw_mng_features_get(struct ethtool_cmis_cdb *cdb, | |||
| 	 */ | ||||
| 	cdb->read_write_len_ext = rpl->read_write_len_ext; | ||||
| 	fw_mng->start_cmd_payload_size = rpl->start_cmd_payload_size; | ||||
| 	fw_mng->write_mechanism = | ||||
| 		rpl->write_mechanism == CMIS_CDB_FW_WRITE_MECHANISM_LPL ? | ||||
| 		CMIS_CDB_FW_WRITE_MECHANISM_LPL : | ||||
| 		CMIS_CDB_FW_WRITE_MECHANISM_EPL; | ||||
| 	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); | ||||
|  | @ -149,7 +155,7 @@ struct cmis_cdb_write_fw_block_lpl_pl { | |||
| }; | ||||
| 
 | ||||
| static int | ||||
| cmis_fw_update_write_image(struct ethtool_cmis_cdb *cdb, | ||||
| cmis_fw_update_write_image_lpl(struct ethtool_cmis_cdb *cdb, | ||||
| 			       struct ethtool_cmis_fw_update_params *fw_update, | ||||
| 			       struct cmis_fw_update_fw_mng_features *fw_mng) | ||||
| { | ||||
|  | @ -202,6 +208,67 @@ cmis_fw_update_write_image(struct ethtool_cmis_cdb *cdb, | |||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| struct cmis_cdb_write_fw_block_epl_pl { | ||||
| 	u8 fw_block[ETHTOOL_CMIS_CDB_EPL_MAX_PL_LENGTH]; | ||||
| }; | ||||
| 
 | ||||
| static int | ||||
| cmis_fw_update_write_image_epl(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 image_size = fw_update->fw->size; | ||||
| 	u32 offset, lpl_len; | ||||
| 	int err; | ||||
| 
 | ||||
| 	lpl_len = sizeof_field(struct cmis_cdb_write_fw_block_lpl_pl, | ||||
| 			       block_address); | ||||
| 
 | ||||
| 	for (offset = start; offset < image_size; | ||||
| 	     offset += ETHTOOL_CMIS_CDB_EPL_MAX_PL_LENGTH) { | ||||
| 		struct cmis_cdb_write_fw_block_lpl_pl lpl = { | ||||
| 			.block_address = cpu_to_be32(offset - start), | ||||
| 		}; | ||||
| 		struct cmis_cdb_write_fw_block_epl_pl *epl; | ||||
| 		struct ethtool_cmis_cdb_cmd_args args = {}; | ||||
| 		u32 epl_len; | ||||
| 
 | ||||
| 		ethnl_module_fw_flash_ntf_in_progress(fw_update->dev, | ||||
| 						      &fw_update->ntf_params, | ||||
| 						      offset - start, | ||||
| 						      image_size); | ||||
| 
 | ||||
| 		epl_len = min_t(u32, ETHTOOL_CMIS_CDB_EPL_MAX_PL_LENGTH, | ||||
| 				image_size - offset); | ||||
| 		epl = kmalloc_array(epl_len, sizeof(u8), GFP_KERNEL); | ||||
| 		if (!epl) | ||||
| 			return -ENOMEM; | ||||
| 
 | ||||
| 		memcpy(epl->fw_block, &fw_update->fw->data[offset], epl_len); | ||||
| 
 | ||||
| 		ethtool_cmis_cdb_compose_args(&args, | ||||
| 					      ETHTOOL_CMIS_CDB_CMD_WRITE_FW_BLOCK_EPL, | ||||
| 					      (u8 *)&lpl, lpl_len, (u8 *)epl, | ||||
| 					      epl_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); | ||||
| 		kfree(epl); | ||||
| 		if (err < 0) { | ||||
| 			ethnl_module_fw_flash_ntf_err(fw_update->dev, | ||||
| 						      &fw_update->ntf_params, | ||||
| 						      "Write FW block EPL 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, | ||||
|  | @ -238,9 +305,15 @@ cmis_fw_update_download_image(struct ethtool_cmis_cdb *cdb, | |||
| 	if (err < 0) | ||||
| 		return err; | ||||
| 
 | ||||
| 	err = cmis_fw_update_write_image(cdb, fw_update, fw_mng); | ||||
| 	if (fw_mng->write_mechanism == CMIS_CDB_FW_WRITE_MECHANISM_LPL) { | ||||
| 		err = cmis_fw_update_write_image_lpl(cdb, fw_update, fw_mng); | ||||
| 		if (err < 0) | ||||
| 			return err; | ||||
| 	} else { | ||||
| 		err = cmis_fw_update_write_image_epl(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); | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue
	
	 Danielle Ratson
						Danielle Ratson