forked from mirrors/linux
		
	ice: Implement driver functionality to dump serdes equalizer values
To debug link issues in the field, serdes Tx/Rx equalizer values
help to determine the health of serdes lane.
Extend 'ethtool -d' option to dump serdes Tx/Rx equalizer.
The following list of equalizer param is supported
    a. rx_equalization_pre2
    b. rx_equalization_pre1
    c. rx_equalization_post1
    d. rx_equalization_bflf
    e. rx_equalization_bfhf
    f. rx_equalization_drate
    g. tx_equalization_pre1
    h. tx_equalization_pre3
    i. tx_equalization_atten
    j. tx_equalization_post1
    k. tx_equalization_pre2
Reviewed-by: Simon Horman <horms@kernel.org>
Reviewed-by: Jesse Brandeburg <jesse.brandeburg@intel.com>
Signed-off-by: Anil Samal <anil.samal@intel.com>
Tested-by: Pucha Himasekhar Reddy <himasekharx.reddy.pucha@intel.com> (A Contingent worker at Intel)
Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
Link: https://patch.msgid.link/20240709202951.2103115-4-anthony.l.nguyen@intel.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
			
			
This commit is contained in:
		
							parent
							
								
									ac21add254
								
							
						
					
					
						commit
						70838938e8
					
				
					 5 changed files with 245 additions and 2 deletions
				
			
		|  | @ -1461,6 +1461,55 @@ struct ice_aqc_get_sensor_reading_resp { | |||
| 	} data; | ||||
| }; | ||||
| 
 | ||||
| /* DNL call command (indirect 0x0682)
 | ||||
|  * Struct is used for both command and response | ||||
|  */ | ||||
| struct ice_aqc_dnl_call_command { | ||||
| 	u8 ctx; /* Used in command, reserved in response */ | ||||
| 	u8 reserved; | ||||
| 	__le16 activity_id; | ||||
| #define ICE_AQC_ACT_ID_DNL 0x1129 | ||||
| 	__le32 reserved1; | ||||
| 	__le32 addr_high; | ||||
| 	__le32 addr_low; | ||||
| }; | ||||
| 
 | ||||
| struct ice_aqc_dnl_equa_param { | ||||
| 	__le16 data_in; | ||||
| #define ICE_AQC_RX_EQU_SHIFT 8 | ||||
| #define ICE_AQC_RX_EQU_PRE2 (0x10 << ICE_AQC_RX_EQU_SHIFT) | ||||
| #define ICE_AQC_RX_EQU_PRE1 (0x11 << ICE_AQC_RX_EQU_SHIFT) | ||||
| #define ICE_AQC_RX_EQU_POST1 (0x12 << ICE_AQC_RX_EQU_SHIFT) | ||||
| #define ICE_AQC_RX_EQU_BFLF (0x13 << ICE_AQC_RX_EQU_SHIFT) | ||||
| #define ICE_AQC_RX_EQU_BFHF (0x14 << ICE_AQC_RX_EQU_SHIFT) | ||||
| #define ICE_AQC_RX_EQU_DRATE (0x15 << ICE_AQC_RX_EQU_SHIFT) | ||||
| #define ICE_AQC_TX_EQU_PRE1 0x0 | ||||
| #define ICE_AQC_TX_EQU_PRE3 0x3 | ||||
| #define ICE_AQC_TX_EQU_ATTEN 0x4 | ||||
| #define ICE_AQC_TX_EQU_POST1 0x8 | ||||
| #define ICE_AQC_TX_EQU_PRE2 0xC | ||||
| 	__le16 op_code_serdes_sel; | ||||
| #define ICE_AQC_OP_CODE_SHIFT 4 | ||||
| #define ICE_AQC_OP_CODE_RX_EQU (0x9 << ICE_AQC_OP_CODE_SHIFT) | ||||
| #define ICE_AQC_OP_CODE_TX_EQU (0x10 << ICE_AQC_OP_CODE_SHIFT) | ||||
| 	__le32 reserved[3]; | ||||
| }; | ||||
| 
 | ||||
| struct ice_aqc_dnl_equa_respon { | ||||
| 	/* Equalization value can be negative */ | ||||
| 	int val; | ||||
| 	__le32 reserved[3]; | ||||
| }; | ||||
| 
 | ||||
| /* DNL call command/response buffer (indirect 0x0682) */ | ||||
| struct ice_aqc_dnl_call { | ||||
| 	union { | ||||
| 		struct ice_aqc_dnl_equa_param txrx_equa_reqs; | ||||
| 		__le32 stores[4]; | ||||
| 		struct ice_aqc_dnl_equa_respon txrx_equa_resp; | ||||
| 	} sto; | ||||
| }; | ||||
| 
 | ||||
| struct ice_aqc_link_topo_params { | ||||
| 	u8 lport_num; | ||||
| 	u8 lport_num_valid; | ||||
|  | @ -2564,6 +2613,7 @@ struct ice_aq_desc { | |||
| 		struct ice_aqc_get_link_status get_link_status; | ||||
| 		struct ice_aqc_event_lan_overflow lan_overflow; | ||||
| 		struct ice_aqc_get_link_topo get_link_topo; | ||||
| 		struct ice_aqc_dnl_call_command dnl_call; | ||||
| 		struct ice_aqc_i2c read_write_i2c; | ||||
| 		struct ice_aqc_read_i2c_resp read_i2c_resp; | ||||
| 		struct ice_aqc_get_set_tx_topo get_set_tx_topo; | ||||
|  | @ -2688,6 +2738,7 @@ enum ice_adminq_opc { | |||
| 	ice_aqc_opc_set_phy_rec_clk_out			= 0x0630, | ||||
| 	ice_aqc_opc_get_phy_rec_clk_out			= 0x0631, | ||||
| 	ice_aqc_opc_get_sensor_reading			= 0x0632, | ||||
| 	ice_aqc_opc_dnl_call                            = 0x0682, | ||||
| 	ice_aqc_opc_get_link_topo			= 0x06E0, | ||||
| 	ice_aqc_opc_read_i2c				= 0x06E2, | ||||
| 	ice_aqc_opc_write_i2c				= 0x06E3, | ||||
|  |  | |||
|  | @ -3371,6 +3371,43 @@ int ice_update_link_info(struct ice_port_info *pi) | |||
| 	return status; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * ice_aq_get_phy_equalization - function to read serdes equaliser | ||||
|  * value from firmware using admin queue command. | ||||
|  * @hw: pointer to the HW struct | ||||
|  * @data_in: represents the serdes equalization parameter requested | ||||
|  * @op_code: represents the serdes number and flag to represent tx or rx | ||||
|  * @serdes_num: represents the serdes number | ||||
|  * @output: pointer to the caller-supplied buffer to return serdes equaliser | ||||
|  * | ||||
|  * Return: non-zero status on error and 0 on success. | ||||
|  */ | ||||
| int ice_aq_get_phy_equalization(struct ice_hw *hw, u16 data_in, u16 op_code, | ||||
| 				u8 serdes_num, int *output) | ||||
| { | ||||
| 	struct ice_aqc_dnl_call_command *cmd; | ||||
| 	struct ice_aqc_dnl_call buf = {}; | ||||
| 	struct ice_aq_desc desc; | ||||
| 	int err; | ||||
| 
 | ||||
| 	buf.sto.txrx_equa_reqs.data_in = cpu_to_le16(data_in); | ||||
| 	buf.sto.txrx_equa_reqs.op_code_serdes_sel = | ||||
| 		cpu_to_le16(op_code | (serdes_num & 0xF)); | ||||
| 	cmd = &desc.params.dnl_call; | ||||
| 	ice_fill_dflt_direct_cmd_desc(&desc, ice_aqc_opc_dnl_call); | ||||
| 	desc.flags |= cpu_to_le16(ICE_AQ_FLAG_BUF | | ||||
| 				  ICE_AQ_FLAG_RD | | ||||
| 				  ICE_AQ_FLAG_SI); | ||||
| 	desc.datalen = cpu_to_le16(sizeof(struct ice_aqc_dnl_call)); | ||||
| 	cmd->activity_id = cpu_to_le16(ICE_AQC_ACT_ID_DNL); | ||||
| 
 | ||||
| 	err = ice_aq_send_cmd(hw, &desc, &buf, sizeof(struct ice_aqc_dnl_call), | ||||
| 			      NULL); | ||||
| 	*output = err ? 0 : buf.sto.txrx_equa_resp.val; | ||||
| 
 | ||||
| 	return err; | ||||
| } | ||||
| 
 | ||||
| #define FEC_REG_PORT(port) {	\ | ||||
| 	FEC_CORR_LOW_REG_PORT##port,		\ | ||||
| 	FEC_CORR_HIGH_REG_PORT##port,	\ | ||||
|  |  | |||
|  | @ -142,6 +142,8 @@ int | |||
| ice_get_link_default_override(struct ice_link_default_override_tlv *ldo, | ||||
| 			      struct ice_port_info *pi); | ||||
| bool ice_is_phy_caps_an_enabled(struct ice_aqc_get_phy_caps_data *caps); | ||||
| int ice_aq_get_phy_equalization(struct ice_hw *hw, u16 data_in, u16 op_code, | ||||
| 				u8 serdes_num, int *output); | ||||
| int | ||||
| ice_aq_get_fec_stats(struct ice_hw *hw, u16 pcs_quad, u16 pcs_port, | ||||
| 		     enum ice_fec_stats_types fec_type, u32 *output); | ||||
|  |  | |||
|  | @ -463,7 +463,8 @@ ice_get_drvinfo(struct net_device *netdev, struct ethtool_drvinfo *drvinfo) | |||
| 
 | ||||
| static int ice_get_regs_len(struct net_device __always_unused *netdev) | ||||
| { | ||||
| 	return sizeof(ice_regs_dump_list); | ||||
| 	return (sizeof(ice_regs_dump_list) + | ||||
| 		sizeof(struct ice_regdump_to_ethtool)); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  | @ -681,6 +682,137 @@ static int ice_get_port_topology(struct ice_hw *hw, u8 lport, | |||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * ice_get_tx_rx_equa - read serdes tx rx equaliser param | ||||
|  * @hw: pointer to the HW struct | ||||
|  * @serdes_num: represents the serdes number | ||||
|  * @ptr: structure to read all serdes parameter for given serdes | ||||
|  * | ||||
|  * Return: all serdes equalization parameter supported per serdes number | ||||
|  */ | ||||
| static int ice_get_tx_rx_equa(struct ice_hw *hw, u8 serdes_num, | ||||
| 			      struct ice_serdes_equalization_to_ethtool *ptr) | ||||
| { | ||||
| 	int err; | ||||
| 
 | ||||
| 	err = ice_aq_get_phy_equalization(hw, ICE_AQC_TX_EQU_PRE1, | ||||
| 					  ICE_AQC_OP_CODE_TX_EQU, serdes_num, | ||||
| 					  &ptr->tx_equalization_pre1); | ||||
| 	if (err) | ||||
| 		return err; | ||||
| 
 | ||||
| 	err = ice_aq_get_phy_equalization(hw, ICE_AQC_TX_EQU_PRE3, | ||||
| 					  ICE_AQC_OP_CODE_TX_EQU, serdes_num, | ||||
| 					  &ptr->tx_equalization_pre3); | ||||
| 	if (err) | ||||
| 		return err; | ||||
| 
 | ||||
| 	err = ice_aq_get_phy_equalization(hw, ICE_AQC_TX_EQU_ATTEN, | ||||
| 					  ICE_AQC_OP_CODE_TX_EQU, serdes_num, | ||||
| 					  &ptr->tx_equalization_atten); | ||||
| 	if (err) | ||||
| 		return err; | ||||
| 
 | ||||
| 	err = ice_aq_get_phy_equalization(hw, ICE_AQC_TX_EQU_POST1, | ||||
| 					  ICE_AQC_OP_CODE_TX_EQU, serdes_num, | ||||
| 					  &ptr->tx_equalization_post1); | ||||
| 	if (err) | ||||
| 		return err; | ||||
| 
 | ||||
| 	err = ice_aq_get_phy_equalization(hw, ICE_AQC_TX_EQU_PRE2, | ||||
| 					  ICE_AQC_OP_CODE_TX_EQU, serdes_num, | ||||
| 					  &ptr->tx_equalization_pre2); | ||||
| 	if (err) | ||||
| 		return err; | ||||
| 
 | ||||
| 	err = ice_aq_get_phy_equalization(hw, ICE_AQC_RX_EQU_PRE2, | ||||
| 					  ICE_AQC_OP_CODE_RX_EQU, serdes_num, | ||||
| 					  &ptr->rx_equalization_pre2); | ||||
| 	if (err) | ||||
| 		return err; | ||||
| 
 | ||||
| 	err = ice_aq_get_phy_equalization(hw, ICE_AQC_RX_EQU_PRE1, | ||||
| 					  ICE_AQC_OP_CODE_RX_EQU, serdes_num, | ||||
| 					  &ptr->rx_equalization_pre1); | ||||
| 	if (err) | ||||
| 		return err; | ||||
| 
 | ||||
| 	err = ice_aq_get_phy_equalization(hw, ICE_AQC_RX_EQU_POST1, | ||||
| 					  ICE_AQC_OP_CODE_RX_EQU, serdes_num, | ||||
| 					  &ptr->rx_equalization_post1); | ||||
| 	if (err) | ||||
| 		return err; | ||||
| 
 | ||||
| 	err = ice_aq_get_phy_equalization(hw, ICE_AQC_RX_EQU_BFLF, | ||||
| 					  ICE_AQC_OP_CODE_RX_EQU, serdes_num, | ||||
| 					  &ptr->rx_equalization_bflf); | ||||
| 	if (err) | ||||
| 		return err; | ||||
| 
 | ||||
| 	err = ice_aq_get_phy_equalization(hw, ICE_AQC_RX_EQU_BFHF, | ||||
| 					  ICE_AQC_OP_CODE_RX_EQU, serdes_num, | ||||
| 					  &ptr->rx_equalization_bfhf); | ||||
| 	if (err) | ||||
| 		return err; | ||||
| 
 | ||||
| 	err = ice_aq_get_phy_equalization(hw, ICE_AQC_RX_EQU_DRATE, | ||||
| 					  ICE_AQC_OP_CODE_RX_EQU, serdes_num, | ||||
| 					  &ptr->rx_equalization_drate); | ||||
| 	if (err) | ||||
| 		return err; | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * ice_get_extended_regs - returns FEC correctable, uncorrectable stats per | ||||
|  *                         pcsquad, pcsport | ||||
|  * @netdev: pointer to net device structure | ||||
|  * @p: output buffer to fill requested register dump | ||||
|  * | ||||
|  * Return: 0 on success, negative on failure. | ||||
|  */ | ||||
| static int ice_get_extended_regs(struct net_device *netdev, void *p) | ||||
| { | ||||
| 	struct ice_netdev_priv *np = netdev_priv(netdev); | ||||
| 	struct ice_regdump_to_ethtool *ice_prv_regs_buf; | ||||
| 	struct ice_port_topology port_topology = {}; | ||||
| 	struct ice_port_info *pi; | ||||
| 	struct ice_pf *pf; | ||||
| 	struct ice_hw *hw; | ||||
| 	unsigned int i; | ||||
| 	int err; | ||||
| 
 | ||||
| 	pf = np->vsi->back; | ||||
| 	hw = &pf->hw; | ||||
| 	pi = np->vsi->port_info; | ||||
| 
 | ||||
| 	/* Serdes parameters are not supported if not the PF VSI */ | ||||
| 	if (np->vsi->type != ICE_VSI_PF || !pi) | ||||
| 		return -EINVAL; | ||||
| 
 | ||||
| 	err = ice_get_port_topology(hw, pi->lport, &port_topology); | ||||
| 	if (err) | ||||
| 		return -EINVAL; | ||||
| 	if (port_topology.serdes_lane_count > 4) | ||||
| 		return -EINVAL; | ||||
| 
 | ||||
| 	ice_prv_regs_buf = p; | ||||
| 
 | ||||
| 	/* Get serdes equalization parameter for available serdes */ | ||||
| 	for (i = 0; i < port_topology.serdes_lane_count; i++) { | ||||
| 		u8 serdes_num = 0; | ||||
| 
 | ||||
| 		serdes_num = port_topology.primary_serdes_lane + i; | ||||
| 		err = ice_get_tx_rx_equa(hw, serdes_num, | ||||
| 					 &ice_prv_regs_buf->equalization[i]); | ||||
| 		if (err) | ||||
| 			return -EINVAL; | ||||
| 	} | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| ice_get_regs(struct net_device *netdev, struct ethtool_regs *regs, void *p) | ||||
| { | ||||
|  | @ -690,10 +822,12 @@ ice_get_regs(struct net_device *netdev, struct ethtool_regs *regs, void *p) | |||
| 	u32 *regs_buf = (u32 *)p; | ||||
| 	unsigned int i; | ||||
| 
 | ||||
| 	regs->version = 1; | ||||
| 	regs->version = 2; | ||||
| 
 | ||||
| 	for (i = 0; i < ARRAY_SIZE(ice_regs_dump_list); ++i) | ||||
| 		regs_buf[i] = rd32(hw, ice_regs_dump_list[i]); | ||||
| 
 | ||||
| 	ice_get_extended_regs(netdev, (void *)®s_buf[i]); | ||||
| } | ||||
| 
 | ||||
| static u32 ice_get_msglevel(struct net_device *netdev) | ||||
|  |  | |||
|  | @ -9,6 +9,25 @@ struct ice_phy_type_to_ethtool { | |||
| 	u8 link_mode; | ||||
| }; | ||||
| 
 | ||||
| struct ice_serdes_equalization_to_ethtool { | ||||
| 	int rx_equalization_pre2; | ||||
| 	int rx_equalization_pre1; | ||||
| 	int rx_equalization_post1; | ||||
| 	int rx_equalization_bflf; | ||||
| 	int rx_equalization_bfhf; | ||||
| 	int rx_equalization_drate; | ||||
| 	int tx_equalization_pre1; | ||||
| 	int tx_equalization_pre3; | ||||
| 	int tx_equalization_atten; | ||||
| 	int tx_equalization_post1; | ||||
| 	int tx_equalization_pre2; | ||||
| }; | ||||
| 
 | ||||
| struct ice_regdump_to_ethtool { | ||||
| 	/* A multilane port can have max 4 serdes */ | ||||
| 	struct ice_serdes_equalization_to_ethtool equalization[4]; | ||||
| }; | ||||
| 
 | ||||
| /* Port topology from lport i.e.
 | ||||
|  * serdes mapping, pcsquad, macport, cage etc... | ||||
|  */ | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue
	
	 Anil Samal
						Anil Samal