staticconstchar ice_gstrings_test[][ETH_GSTRING_LEN] = { "Register test (offline)", "EEPROM test (offline)", "Interrupt test (offline)", "Loopback test (offline)", "Link test (on/offline)",
};
/* Display NVM version (from which the firmware version can be * determined) which contains more pertinent information.
*/
snprintf(drvinfo->fw_version, sizeof(drvinfo->fw_version), "%x.%02x 0x%x %d.%d.%d", nvm->major, nvm->minor,
nvm->eetrack, orom->major, orom->build, orom->patch);
/** * ice_ethtool_get_maxspeed - Get the max speed for given lport * @hw: pointer to the HW struct * @lport: logical port for which max speed is requested * @max_speed: return max speed for input lport * * Return: 0 on success, negative on failure.
*/ staticint ice_ethtool_get_maxspeed(struct ice_hw *hw, u8 lport, u8 *max_speed)
{ struct ice_aqc_get_port_options_elem options[ICE_AQC_PORT_OPT_MAX] = {}; bool active_valid = false, pending_valid = true;
u8 option_count = ICE_AQC_PORT_OPT_MAX;
u8 active_idx = 0, pending_idx = 0; int status;
status = ice_aq_get_port_options(hw, options, &option_count, lport, true, &active_idx, &active_valid,
&pending_idx, &pending_valid); if (status) return -EIO; if (!active_valid) return -EINVAL;
/** * ice_is_serdes_muxed - returns whether serdes is muxed in hardware * @hw: pointer to the HW struct * * Return: true when serdes is muxed, false when serdes is not muxed.
*/ staticbool ice_is_serdes_muxed(struct ice_hw *hw)
{
u32 reg_value = rd32(hw, GLGEN_SWITCH_MODE_CONFIG);
/** * ice_active_vfs - check if there are any active VFs * @pf: board private structure * * Returns true if an active VF is found, otherwise returns false
*/ staticbool ice_active_vfs(struct ice_pf *pf)
{ bool active = false; struct ice_vf *vf; unsignedint bkt;
rcu_read_lock();
ice_for_each_vf_rcu(pf, bkt, vf) { if (test_bit(ICE_VF_STATE_ACTIVE, vf->vf_states)) {
active = true; break;
}
}
rcu_read_unlock();
return active;
}
/** * ice_link_test - perform a link test on a given net_device * @netdev: network interface device structure * * This function performs one of the self-tests required by ethtool. * Returns 0 on success, non-zero on failure.
*/ static u64 ice_link_test(struct net_device *netdev)
{ struct ice_netdev_priv *np = netdev_priv(netdev); bool link_up = false; int status;
netdev_info(netdev, "link test\n");
status = ice_get_link_status(np->vsi->port_info, &link_up); if (status) {
netdev_err(netdev, "link query error, status = %d\n",
status); return 1;
}
if (!link_up) return 2;
return 0;
}
/** * ice_eeprom_test - perform an EEPROM test on a given net_device * @netdev: network interface device structure * * This function performs one of the self-tests required by ethtool. * Returns 0 on success, non-zero on failure.
*/ static u64 ice_eeprom_test(struct net_device *netdev)
{ struct ice_netdev_priv *np = netdev_priv(netdev); struct ice_pf *pf = np->vsi->back;
/* bail on failure (non-zero return) */ if (ice_reg_pattern_test(hw, reg, mask)) return 1;
}
}
return 0;
}
/** * ice_lbtest_prepare_rings - configure Tx/Rx test rings * @vsi: pointer to the VSI structure * * Function configures rings of a VSI for loopback test without * enabling interrupts or informing the kernel about new queues. * * Returns 0 on success, negative on failure.
*/ staticint ice_lbtest_prepare_rings(struct ice_vsi *vsi)
{ int status;
status = ice_vsi_setup_tx_rings(vsi); if (status) goto err_setup_tx_ring;
status = ice_vsi_setup_rx_rings(vsi); if (status) goto err_setup_rx_ring;
status = ice_vsi_cfg_lan(vsi); if (status) goto err_setup_rx_ring;
status = ice_vsi_start_all_rx_rings(vsi); if (status) goto err_start_rx_ring;
/** * ice_lbtest_disable_rings - disable Tx/Rx test rings after loopback test * @vsi: pointer to the VSI structure * * Function stops and frees VSI rings after a loopback test. * Returns 0 on success, negative on failure.
*/ staticint ice_lbtest_disable_rings(struct ice_vsi *vsi)
{ int status;
status = ice_vsi_stop_lan_tx_rings(vsi, ICE_NO_RESET, 0); if (status)
netdev_err(vsi->netdev, "Failed to stop Tx rings, VSI %d error %d\n",
vsi->vsi_num, status);
status = ice_vsi_stop_all_rx_rings(vsi); if (status)
netdev_err(vsi->netdev, "Failed to stop Rx rings, VSI %d error %d\n",
vsi->vsi_num, status);
/** * ice_lbtest_create_frame - create test packet * @pf: pointer to the PF structure * @ret_data: allocated frame buffer * @size: size of the packet data * * Function allocates a frame with a test pattern on specific offsets. * Returns 0 on success, non-zero on failure.
*/ staticint ice_lbtest_create_frame(struct ice_pf *pf, u8 **ret_data, u16 size)
{
u8 *data;
if (!pf) return -EINVAL;
data = kzalloc(size, GFP_KERNEL); if (!data) return -ENOMEM;
/* Since the ethernet test frame should always be at least * 64 bytes long, fill some octets in the payload with test data.
*/
memset(data, 0xFF, size);
data[32] = 0xDE;
data[42] = 0xAD;
data[44] = 0xBE;
data[46] = 0xEF;
*ret_data = data;
return 0;
}
/** * ice_lbtest_check_frame - verify received loopback frame * @frame: pointer to the raw packet data * * Function verifies received test frame with a pattern. * Returns true if frame matches the pattern, false otherwise.
*/ staticbool ice_lbtest_check_frame(u8 *frame)
{ /* Validate bytes of a frame under offsets chosen earlier */ if (frame[32] == 0xDE &&
frame[42] == 0xAD &&
frame[44] == 0xBE &&
frame[46] == 0xEF &&
frame[48] == 0xFF) returntrue;
returnfalse;
}
/** * ice_diag_send - send test frames to the test ring * @tx_ring: pointer to the transmit ring * @data: pointer to the raw packet data * @size: size of the packet to send * * Function sends loopback packets on a test Tx ring.
*/ staticint ice_diag_send(struct ice_tx_ring *tx_ring, u8 *data, u16 size)
{ struct ice_tx_desc *tx_desc; struct ice_tx_buf *tx_buf;
dma_addr_t dma;
u64 td_cmd;
/* Wait until the packets get transmitted to the receive queue. */
usleep_range(1000, 2000);
dma_unmap_single(tx_ring->dev, dma, size, DMA_TO_DEVICE);
return 0;
}
#define ICE_LB_FRAME_SIZE 64 /** * ice_lbtest_receive_frames - receive and verify test frames * @rx_ring: pointer to the receive ring * * Function receives loopback packets and verify their correctness. * Returns number of received valid frames.
*/ staticint ice_lbtest_receive_frames(struct ice_rx_ring *rx_ring)
{ struct ice_rx_buf *rx_buf; int valid_frames, i;
u8 *received_buf;
valid_frames = 0;
for (i = 0; i < rx_ring->count; i++) { union ice_32b_rx_flex_desc *rx_desc;
rx_desc = ICE_RX_DESC(rx_ring, i);
if (!(rx_desc->wb.status_error0 &
(cpu_to_le16(BIT(ICE_RX_FLEX_DESC_STATUS0_DD_S)) |
cpu_to_le16(BIT(ICE_RX_FLEX_DESC_STATUS0_EOF_S))))) continue;
if (ice_lbtest_prepare_rings(test_vsi)) {
ret = 2; goto lbtest_vsi_close;
}
if (ice_alloc_rx_bufs(rx_ring, rx_ring->count)) {
ret = 3; goto lbtest_rings_dis;
}
/* Enable MAC loopback in firmware */ if (ice_aq_set_mac_loopback(&pf->hw, true, NULL)) {
ret = 4; goto lbtest_mac_dis;
}
/* Test VSI needs to receive broadcast packets */
eth_broadcast_addr(broadcast); if (ice_fltr_add_mac(test_vsi, broadcast, ICE_FWD_TO_VSI)) {
ret = 5; goto lbtest_mac_dis;
}
if (ice_lbtest_create_frame(pf, &tx_frame, ICE_LB_FRAME_SIZE)) {
ret = 7; goto remove_mac_filters;
}
num_frames = min_t(int, tx_ring->count, 32); for (i = 0; i < num_frames; i++) { if (ice_diag_send(tx_ring, tx_frame, ICE_LB_FRAME_SIZE)) {
ret = 8; goto remove_mac_filters;
}
}
valid_frames = ice_lbtest_receive_frames(rx_ring); if (!valid_frames)
ret = 9; elseif (valid_frames != num_frames)
ret = 10;
remove_mac_filters: if (ice_fltr_remove_mac(test_vsi, broadcast, ICE_FWD_TO_VSI))
netdev_err(netdev, "Could not remove MAC filter for the test VSI\n");
lbtest_mac_dis: /* Disable MAC loopback after the test is completed. */ if (ice_aq_set_mac_loopback(&pf->hw, false, NULL))
netdev_err(netdev, "Could not disable MAC loopback\n");
lbtest_rings_dis: if (ice_lbtest_disable_rings(test_vsi))
netdev_err(netdev, "Could not disable test rings\n");
lbtest_vsi_close:
test_vsi->netdev = NULL; if (ice_vsi_release(test_vsi))
netdev_err(netdev, "Failed to remove the test VSI\n");
return ret;
}
/** * ice_intr_test - perform an interrupt test on a given net_device * @netdev: network interface device structure * * This function performs one of the self-tests required by ethtool. * Returns 0 on success, non-zero on failure.
*/ static u64 ice_intr_test(struct net_device *netdev)
{ struct ice_netdev_priv *np = netdev_priv(netdev); struct ice_pf *pf = np->vsi->back;
u16 swic_old = pf->sw_int_count;
/** * ice_self_test - handler function for performing a self-test by ethtool * @netdev: network interface device structure * @eth_test: ethtool_test structure * @data: required by ethtool.self_test * * This function is called after invoking 'ethtool -t devname' command where * devname is the name of the network device on which ethtool should operate. * It performs a set of self-tests to check if a device works properly.
*/ staticvoid
ice_self_test(struct net_device *netdev, struct ethtool_test *eth_test,
u64 *data)
{ struct ice_netdev_priv *np = netdev_priv(netdev); bool if_running = netif_running(netdev); struct ice_pf *pf = np->vsi->back; struct device *dev;
dev = ice_pf_to_dev(pf);
if (eth_test->flags == ETH_TEST_FL_OFFLINE) {
netdev_info(netdev, "offline testing starting\n");
set_bit(ICE_TESTING, pf->state);
if (ice_active_vfs(pf)) {
dev_warn(dev, "Please take active VFs and Netqueues offline and restart the adapter before running NIC diagnostics\n");
data[ICE_ETH_TEST_REG] = 1;
data[ICE_ETH_TEST_EEPROM] = 1;
data[ICE_ETH_TEST_INTR] = 1;
data[ICE_ETH_TEST_LOOP] = 1;
data[ICE_ETH_TEST_LINK] = 1;
eth_test->flags |= ETH_TEST_FL_FAILED;
clear_bit(ICE_TESTING, pf->state); goto skip_ol_tests;
} /* If the device is online then take it offline */ if (if_running) /* indicate we're in test mode */
ice_stop(netdev);
if (status) {
dev_err(dev, "Could not open device %s, err %d\n",
pf->int_name, status);
}
}
} else { /* Online tests */
netdev_info(netdev, "online testing starting\n");
data[ICE_ETH_TEST_LINK] = ice_link_test(netdev); if (data[ICE_ETH_TEST_LINK])
eth_test->flags |= ETH_TEST_FL_FAILED;
/* Offline only tests, not run in online; pass by default */
data[ICE_ETH_TEST_REG] = 0;
data[ICE_ETH_TEST_EEPROM] = 0;
data[ICE_ETH_TEST_INTR] = 0;
data[ICE_ETH_TEST_LOOP] = 0;
}
/* Changing the FEC parameters is not supported if not the PF VSI */ if (vsi->type != ICE_VSI_PF) {
netdev_info(netdev, "Changing FEC parameters only supported for PF VSI\n"); return -EOPNOTSUPP;
}
/* Proceed only if requesting different FEC mode */ if (pi->phy.curr_user_fec_req == req_fec) return 0;
/* Copy the current user PHY configuration. The current user PHY * configuration is initialized during probe from PHY capabilities * software mode, and updated on set PHY configuration.
*/
memcpy(&config, &pi->phy.curr_user_phy_cfg, sizeof(config));
if (!pi) return -EOPNOTSUPP;
link_info = &pi->phy.link_info;
/* Set FEC mode based on negotiated link info */ switch (link_info->fec_info) { case ICE_AQ_LINK_25G_KR_FEC_EN:
fecparam->active_fec = ETHTOOL_FEC_BASER; break; case ICE_AQ_LINK_25G_RS_528_FEC_EN: case ICE_AQ_LINK_25G_RS_544_FEC_EN:
fecparam->active_fec = ETHTOOL_FEC_RS; break; default:
fecparam->active_fec = ETHTOOL_FEC_OFF; break;
}
caps = kzalloc(sizeof(*caps), GFP_KERNEL); if (!caps) return -ENOMEM;
/* If VSI state is up, then restart autoneg with link up */ if (!test_bit(ICE_DOWN, vsi->back->state))
err = ice_set_link(vsi, true); else
err = ice_set_link(vsi, false);
return err;
}
/** * ice_get_priv_flags - report device private flags * @netdev: network interface device structure * * The get string set count and the string set should be matched for each * flag returned. Add new strings for each flag to the ice_gstrings_priv_flags * array. * * Returns a u32 bitmap of flags.
*/ static u32 ice_get_priv_flags(struct net_device *netdev)
{ struct ice_netdev_priv *np = netdev_priv(netdev); struct ice_vsi *vsi = np->vsi; struct ice_pf *pf = vsi->back;
u32 i, ret_flags = 0;
for (i = 0; i < ICE_PRIV_FLAG_ARRAY_SIZE; i++) { conststruct ice_priv_flag *priv_flag;
priv_flag = &ice_gstrings_priv_flags[i];
if (test_bit(priv_flag->bitno, pf->flags))
ret_flags |= BIT(i);
}
return ret_flags;
}
/** * ice_set_priv_flags - set private flags * @netdev: network interface device structure * @flags: bit flags to be set
*/ staticint ice_set_priv_flags(struct net_device *netdev, u32 flags)
{ struct ice_netdev_priv *np = netdev_priv(netdev);
DECLARE_BITMAP(change_flags, ICE_PF_FLAGS_NBITS);
DECLARE_BITMAP(orig_flags, ICE_PF_FLAGS_NBITS); struct ice_vsi *vsi = np->vsi; struct ice_pf *pf = vsi->back; struct device *dev; int ret = 0;
u32 i;
if (flags > BIT(ICE_PRIV_FLAG_ARRAY_SIZE)) return -EINVAL;
dev = ice_pf_to_dev(pf);
set_bit(ICE_FLAG_ETHTOOL_CTXT, pf->flags);
bitmap_copy(orig_flags, pf->flags, ICE_PF_FLAGS_NBITS); for (i = 0; i < ICE_PRIV_FLAG_ARRAY_SIZE; i++) { conststruct ice_priv_flag *priv_flag;
priv_flag = &ice_gstrings_priv_flags[i];
if (flags & BIT(i))
set_bit(priv_flag->bitno, pf->flags); else
clear_bit(priv_flag->bitno, pf->flags);
}
/* Do not allow change to link-down-on-close when Total Port Shutdown * is enabled.
*/ if (test_bit(ICE_FLAG_LINK_DOWN_ON_CLOSE_ENA, change_flags) &&
test_bit(ICE_FLAG_TOTAL_PORT_SHUTDOWN_ENA, pf->flags)) {
dev_err(dev, "Setting link-down-on-close not supported on this port\n");
set_bit(ICE_FLAG_LINK_DOWN_ON_CLOSE_ENA, pf->flags);
ret = -EINVAL; goto ethtool_exit;
}
if (test_bit(ICE_FLAG_FW_LLDP_AGENT, change_flags)) { if (!test_bit(ICE_FLAG_FW_LLDP_AGENT, pf->flags)) { int status;
/* Disable FW LLDP engine */
status = ice_cfg_lldp_mib_change(&pf->hw, false);
/* If unregistering for LLDP events fails, this is * not an error state, as there shouldn't be any * events to respond to.
*/ if (status)
dev_info(dev, "Failed to unreg for LLDP events\n");
/* The AQ call to stop the FW LLDP agent will generate * an error if the agent is already stopped.
*/
status = ice_aq_stop_lldp(&pf->hw, true, true, NULL); if (status)
dev_warn(dev, "Fail to stop LLDP agent\n"); /* Use case for having the FW LLDP agent stopped * will likely not need DCB, so failure to init is * not a concern of ethtool
*/
status = ice_init_pf_dcb(pf, true); if (status)
dev_warn(dev, "Fail to init DCB\n");
if (ice_get_pfc_mode(pf) == ICE_QOS_MODE_DSCP) {
clear_bit(ICE_FLAG_FW_LLDP_AGENT, pf->flags);
dev_err(dev, "QoS in L3 DSCP mode, FW Agent not allowed to start\n");
ret = -EOPNOTSUPP; goto ethtool_exit;
}
/* Remove rule to direct LLDP packets to default VSI. * The FW LLDP engine will now be consuming them.
*/
ice_cfg_sw_rx_lldp(vsi->back, false);
/* AQ command to start FW LLDP agent will return an * error if the agent is already started
*/
status = ice_aq_start_lldp(&pf->hw, true, NULL); if (status)
dev_warn(dev, "Fail to start LLDP Agent\n");
/* AQ command to start FW DCBX agent will fail if * the agent is already started
*/
status = ice_aq_start_stop_dcbx(&pf->hw, true,
&dcbx_agent_status,
NULL); if (status)
dev_dbg(dev, "Failed to start FW DCBX\n");
/* Failure to configure MIB change or init DCB is not * relevant to ethtool. Print notification that * registration/init failed but do not return error * state to ethtool
*/
status = ice_init_pf_dcb(pf, true); if (status)
dev_dbg(dev, "Fail to init DCB\n");
/* Register for MIB change events */
status = ice_cfg_lldp_mib_change(&pf->hw, true); if (status)
dev_dbg(dev, "Fail to enable MIB change events\n");
ice_nway_reset(netdev);
}
} if (test_bit(ICE_FLAG_LEGACY_RX, change_flags)) { /* down and up VSI so that changes of Rx cfg are reflected. */
ice_down_up(vsi);
} /* don't allow modification of this flag when a single VF is in * promiscuous mode because it's not supported
*/ if (test_bit(ICE_FLAG_VF_TRUE_PROMISC_ENA, change_flags) &&
ice_is_any_vf_in_unicast_promisc(pf)) {
dev_err(dev, "Changing vf-true-promisc-support flag while VF(s) are in promiscuous mode not supported\n"); /* toggle bit back to previous state */
change_bit(ICE_FLAG_VF_TRUE_PROMISC_ENA, pf->flags);
ret = -EAGAIN;
}
if (test_bit(ICE_FLAG_VF_VLAN_PRUNING, change_flags) &&
ice_has_vfs(pf)) {
dev_err(dev, "vf-vlan-pruning: VLAN pruning cannot be changed while VFs are active.\n"); /* toggle bit back to previous state */
change_bit(ICE_FLAG_VF_VLAN_PRUNING, pf->flags);
ret = -EOPNOTSUPP;
}
ethtool_exit:
clear_bit(ICE_FLAG_ETHTOOL_CTXT, pf->flags); return ret;
}
staticint ice_get_sset_count(struct net_device *netdev, int sset)
{ switch (sset) { case ETH_SS_STATS: /* The number (and order) of strings reported *must* remain * constant for a given netdevice. This function must not * report a different number based on run time parameters * (such as the number of queues in use, or the setting of * a private ethtool flag). This is due to the nature of the * ethtool stats API. * * Userspace programs such as ethtool must make 3 separate * ioctl requests, one for size, one for the strings, and * finally one for the stats. Since these cross into * userspace, changes to the number or size could result in * undefined memory access or incorrect string<->value * correlations for statistics. * * Even if it appears to be safe, changes to the size or * order of strings will suffer from race conditions and are * not safe.
*/ return ICE_ALL_STATS_LEN(netdev); case ETH_SS_TEST: return ICE_TEST_LEN; case ETH_SS_PRIV_FLAGS: return ICE_PRIV_FLAG_ARRAY_SIZE; default: return -EOPNOTSUPP;
}
}
/** * ice_mask_min_supported_speeds * @hw: pointer to the HW structure * @phy_types_high: PHY type high * @phy_types_low: PHY type low to apply minimum supported speeds mask * * Apply minimum supported speeds mask to PHY type low. These are the speeds * for ethtool supported link mode.
*/ staticvoid
ice_mask_min_supported_speeds(struct ice_hw *hw,
u64 phy_types_high, u64 *phy_types_low)
{ /* if QSFP connection with 100G speed, minimum supported speed is 25G */ if ((*phy_types_low & ICE_PHY_TYPE_LOW_MASK_100G) ||
(phy_types_high & ICE_PHY_TYPE_HIGH_MASK_100G) ||
(phy_types_high & ICE_PHY_TYPE_HIGH_MASK_200G))
*phy_types_low &= ~ICE_PHY_TYPE_LOW_MASK_MIN_25G; elseif (!ice_is_100m_speed_supported(hw))
*phy_types_low &= ~ICE_PHY_TYPE_LOW_MASK_MIN_1G;
}
/** * ice_linkmode_set_bit - set link mode bit * @phy_to_ethtool: PHY type to ethtool link mode struct to set * @ks: ethtool link ksettings struct to fill out * @req_speeds: speed requested by user * @advert_phy_type: advertised PHY type * @phy_type: PHY type
*/ staticvoid
ice_linkmode_set_bit(conststruct ice_phy_type_to_ethtool *phy_to_ethtool, struct ethtool_link_ksettings *ks, u32 req_speeds,
u64 advert_phy_type, u32 phy_type)
{
linkmode_set_bit(phy_to_ethtool->link_mode, ks->link_modes.supported);
/* Check if lenient mode is supported and enabled, or in strict mode. * * In lenient mode the Supported link modes are the PHY types without * media. The Advertising link mode is either 1. the user requested * speed, 2. the override PHY mask, or 3. the PHY types with media. * * In strict mode Supported link mode are the PHY type with media, * and Advertising link modes are the media PHY type or the speed * requested by user.
*/ if (test_bit(ICE_FLAG_LINK_LENIENT_MODE_ENA, pf->flags)) {
phy_types_low = le64_to_cpu(pf->nvm_phy_type_lo);
phy_types_high = le64_to_cpu(pf->nvm_phy_type_hi);
ice_mask_min_supported_speeds(&pf->hw, phy_types_high,
&phy_types_low); /* determine advertised modes based on link override only * if it's supported and if the FW doesn't abstract the * driver from having to account for link overrides
*/ if (ice_fw_supports_link_override(&pf->hw) &&
!ice_fw_supports_report_dflt_cfg(&pf->hw)) { struct ice_link_default_override_tlv *ldo;
ldo = &pf->link_dflt_override; /* If override enabled and PHY mask set, then * Advertising link mode is the intersection of the PHY * types without media and the override PHY mask.
*/ if (ldo->options & ICE_LINK_OVERRIDE_EN &&
(ldo->phy_type_low || ldo->phy_type_high)) {
advert_phy_type_lo =
le64_to_cpu(pf->nvm_phy_type_lo) &
ldo->phy_type_low;
advert_phy_type_hi =
le64_to_cpu(pf->nvm_phy_type_hi) &
ldo->phy_type_high;
}
}
} else { /* strict mode */
phy_types_low = vsi->port_info->phy.phy_type_low;
phy_types_high = vsi->port_info->phy.phy_type_high;
}
/* If Advertising link mode PHY type is not using override PHY type, * then use PHY type with media.
*/ if (!advert_phy_type_lo && !advert_phy_type_hi) {
advert_phy_type_lo = vsi->port_info->phy.phy_type_low;
advert_phy_type_hi = vsi->port_info->phy.phy_type_high;
}
for (i = 0; i < ARRAY_SIZE(phy_type_low_lkup); i++) { if (phy_types_low & BIT_ULL(i))
ice_linkmode_set_bit(&phy_type_low_lkup[i], ks,
req_speeds, advert_phy_type_lo,
i);
}
for (i = 0; i < ARRAY_SIZE(phy_type_high_lkup); i++) { if (phy_types_high & BIT_ULL(i))
ice_linkmode_set_bit(&phy_type_high_lkup[i], ks,
req_speeds, advert_phy_type_hi,
i);
}
}
/** * ice_get_settings_link_up - Get Link settings for when link is up * @ks: ethtool ksettings to fill in * @netdev: network interface device structure
*/ staticvoid
ice_get_settings_link_up(struct ethtool_link_ksettings *ks, struct net_device *netdev)
{ struct ice_netdev_priv *np = netdev_priv(netdev); struct ice_port_info *pi = np->vsi->port_info; struct ice_link_status *link_info; struct ice_vsi *vsi = np->vsi;
link_info = &vsi->port_info->phy.link_info;
/* Get supported and advertised settings from PHY ability with media */
ice_phy_type_to_ethtool(netdev, ks);
switch (link_info->link_speed) { case ICE_AQ_LINK_SPEED_200GB:
ks->base.speed = SPEED_200000; break; case ICE_AQ_LINK_SPEED_100GB:
ks->base.speed = SPEED_100000; break; case ICE_AQ_LINK_SPEED_50GB:
ks->base.speed = SPEED_50000; break; case ICE_AQ_LINK_SPEED_40GB:
ks->base.speed = SPEED_40000; break; case ICE_AQ_LINK_SPEED_25GB:
ks->base.speed = SPEED_25000; break; case ICE_AQ_LINK_SPEED_20GB:
ks->base.speed = SPEED_20000; break; case ICE_AQ_LINK_SPEED_10GB:
ks->base.speed = SPEED_10000; break; case ICE_AQ_LINK_SPEED_5GB:
ks->base.speed = SPEED_5000; break; case ICE_AQ_LINK_SPEED_2500MB:
ks->base.speed = SPEED_2500; break; case ICE_AQ_LINK_SPEED_1000MB:
ks->base.speed = SPEED_1000; break; case ICE_AQ_LINK_SPEED_100MB:
ks->base.speed = SPEED_100; break; default:
netdev_info(netdev, "WARNING: Unrecognized link_speed (0x%x).\n",
link_info->link_speed); break;
}
ks->base.duplex = DUPLEX_FULL;
if (link_info->an_info & ICE_AQ_AN_COMPLETED)
ethtool_link_ksettings_add_link_mode(ks, lp_advertising,
Autoneg);
/* Set flow control negotiated Rx/Tx pause */ switch (pi->fc.current_mode) { case ICE_FC_FULL:
ethtool_link_ksettings_add_link_mode(ks, lp_advertising, Pause); break; case ICE_FC_TX_PAUSE:
ethtool_link_ksettings_add_link_mode(ks, lp_advertising, Pause);
ethtool_link_ksettings_add_link_mode(ks, lp_advertising,
Asym_Pause); break; case ICE_FC_RX_PAUSE:
ethtool_link_ksettings_add_link_mode(ks, lp_advertising,
Asym_Pause); break; case ICE_FC_PFC: default:
ethtool_link_ksettings_del_link_mode(ks, lp_advertising, Pause);
ethtool_link_ksettings_del_link_mode(ks, lp_advertising,
Asym_Pause); break;
}
}
/** * ice_get_settings_link_down - Get the Link settings when link is down * @ks: ethtool ksettings to fill in * @netdev: network interface device structure * * Reports link settings that can be determined when link is down
*/ staticvoid
ice_get_settings_link_down(struct ethtool_link_ksettings *ks, struct net_device *netdev)
{ /* link is down and the driver needs to fall back on * supported PHY types to figure out what info to display
*/
ice_phy_type_to_ethtool(netdev, ks);
/* With no link, speed and duplex are unknown */
ks->base.speed = SPEED_UNKNOWN;
ks->base.duplex = DUPLEX_UNKNOWN;
}
/** * ice_get_link_ksettings - Get Link Speed and Duplex settings * @netdev: network interface device structure * @ks: ethtool ksettings * * Reports speed/duplex settings based on media_type
*/ staticint
ice_get_link_ksettings(struct net_device *netdev, struct ethtool_link_ksettings *ks)
{ struct ice_netdev_priv *np = netdev_priv(netdev); struct ice_aqc_get_phy_caps_data *caps; struct ice_link_status *hw_link_info; struct ice_vsi *vsi = np->vsi; int err;
/* set speed and duplex */ if (hw_link_info->link_info & ICE_AQ_LINK_UP)
ice_get_settings_link_up(ks, netdev); else
ice_get_settings_link_down(ks, netdev);
/* Set supported FEC modes based on PHY capability */
ethtool_link_ksettings_add_link_mode(ks, supported, FEC_NONE);
if (caps->link_fec_options & ICE_AQC_PHY_FEC_10G_KR_40G_KR4_EN ||
caps->link_fec_options & ICE_AQC_PHY_FEC_25G_KR_CLAUSE74_EN)
ethtool_link_ksettings_add_link_mode(ks, supported, FEC_BASER); if (caps->link_fec_options & ICE_AQC_PHY_FEC_25G_RS_CLAUSE91_EN)
ethtool_link_ksettings_add_link_mode(ks, supported, FEC_RS);
/* Set supported and advertised autoneg */ if (ice_is_phy_caps_an_enabled(caps)) {
ethtool_link_ksettings_add_link_mode(ks, supported, Autoneg);
ethtool_link_ksettings_add_link_mode(ks, advertising, Autoneg);
}
done:
kfree(caps); return err;
}
/** * ice_speed_to_aq_link - Get AQ link speed by Ethtool forced speed * @speed: ethtool forced speed
*/ static u16 ice_speed_to_aq_link(int speed)
{ int aq_speed;
switch (speed) { case SPEED_10:
aq_speed = ICE_AQ_LINK_SPEED_10MB; break; case SPEED_100:
aq_speed = ICE_AQ_LINK_SPEED_100MB; break; case SPEED_1000:
aq_speed = ICE_AQ_LINK_SPEED_1000MB; break; case SPEED_2500:
aq_speed = ICE_AQ_LINK_SPEED_2500MB; break; case SPEED_5000:
aq_speed = ICE_AQ_LINK_SPEED_5GB; break; case SPEED_10000:
aq_speed = ICE_AQ_LINK_SPEED_10GB; break; case SPEED_20000:
aq_speed = ICE_AQ_LINK_SPEED_20GB; break; case SPEED_25000:
aq_speed = ICE_AQ_LINK_SPEED_25GB; break; case SPEED_40000:
aq_speed = ICE_AQ_LINK_SPEED_40GB; break; case SPEED_50000:
aq_speed = ICE_AQ_LINK_SPEED_50GB; break; case SPEED_100000:
aq_speed = ICE_AQ_LINK_SPEED_100GB; break; default:
aq_speed = ICE_AQ_LINK_SPEED_UNKNOWN; break;
} return aq_speed;
}
for (u32 i = 0; i < ARRAY_SIZE(ice_adv_lnk_speed_maps); i++) {
map = ice_adv_lnk_speed_maps + i; if (linkmode_intersects(ks->link_modes.advertising, map->caps))
adv_link_speed |= ice_speed_to_aq_link(map->speed);
}
return adv_link_speed;
}
/** * ice_setup_autoneg * @p: port info * @ks: ethtool_link_ksettings * @config: configuration that will be sent down to FW * @autoneg_enabled: autonegotiation is enabled or not * @autoneg_changed: will there a change in autonegotiation * @netdev: network interface device structure * * Setup PHY autonegotiation feature
*/ staticint
ice_setup_autoneg(struct ice_port_info *p, struct ethtool_link_ksettings *ks, struct ice_aqc_set_phy_cfg_data *config,
u8 autoneg_enabled, u8 *autoneg_changed, struct net_device *netdev)
{ int err = 0;
*autoneg_changed = 0;
/* Check autoneg */ if (autoneg_enabled == AUTONEG_ENABLE) { /* If autoneg was not already enabled */ if (!(p->phy.link_info.an_info & ICE_AQ_AN_COMPLETED)) { /* If autoneg is not supported, return error */ if (!ethtool_link_ksettings_test_link_mode(ks,
supported,
Autoneg)) {
netdev_info(netdev, "Autoneg not supported on this phy.\n");
err = -EINVAL;
} else { /* Autoneg is allowed to change */
config->caps |= ICE_AQ_PHY_ENA_AUTO_LINK_UPDT;
*autoneg_changed = 1;
}
}
} else { /* If autoneg is currently enabled */ if (p->phy.link_info.an_info & ICE_AQ_AN_COMPLETED) { /* If autoneg is supported 10GBASE_T is the only PHY * that can disable it, so otherwise return error
*/ if (ethtool_link_ksettings_test_link_mode(ks,
supported,
Autoneg)) {
netdev_info(netdev, "Autoneg cannot be disabled on this phy\n");
err = -EINVAL;
} else { /* Autoneg is allowed to change */
config->caps &= ~ICE_AQ_PHY_ENA_AUTO_LINK_UPDT;
*autoneg_changed = 1;
}
}
}
return err;
}
/** * ice_set_phy_type_from_speed - set phy_types based on speeds * and advertised modes * @ks: ethtool link ksettings struct * @phy_type_low: pointer to the lower part of phy_type * @phy_type_high: pointer to the higher part of phy_type * @adv_link_speed: targeted link speeds bitmap
*/ staticvoid
ice_set_phy_type_from_speed(conststruct ethtool_link_ksettings *ks,
u64 *phy_type_low, u64 *phy_type_high,
u16 adv_link_speed)
{ /* Handle 1000M speed in a special way because ice_update_phy_type * enables all link modes, but having mixed copper and optical * standards is not supported.
*/
adv_link_speed &= ~ICE_AQ_LINK_SPEED_1000MB;
if (ethtool_link_ksettings_test_link_mode(ks, advertising,
1000baseT_Full))
*phy_type_low |= ICE_PHY_TYPE_LOW_1000BASE_T |
ICE_PHY_TYPE_LOW_1G_SGMII;
if (ethtool_link_ksettings_test_link_mode(ks, advertising,
1000baseKX_Full))
*phy_type_low |= ICE_PHY_TYPE_LOW_1000BASE_KX;
if (ethtool_link_ksettings_test_link_mode(ks, advertising,
1000baseX_Full))
*phy_type_low |= ICE_PHY_TYPE_LOW_1000BASE_SX |
ICE_PHY_TYPE_LOW_1000BASE_LX;
phy_caps = kzalloc(sizeof(*phy_caps), GFP_KERNEL); if (!phy_caps) return -ENOMEM;
/* Get the PHY capabilities based on media */ if (ice_fw_supports_report_dflt_cfg(pi->hw))
err = ice_aq_get_phy_caps(pi, false, ICE_AQC_REPORT_DFLT_CFG,
phy_caps, NULL); else
err = ice_aq_get_phy_caps(pi, false, ICE_AQC_REPORT_TOPO_CAP_MEDIA,
phy_caps, NULL); if (err) goto done;
/* save autoneg out of ksettings */
autoneg = copy_ks.base.autoneg;
/* Get link modes supported by hardware.*/
ice_phy_type_to_ethtool(netdev, &safe_ks);
/* and check against modes requested by user. * Return an error if unsupported mode was set.
*/ if (!bitmap_subset(copy_ks.link_modes.advertising,
safe_ks.link_modes.supported,
__ETHTOOL_LINK_MODE_MASK_NBITS)) { if (!test_bit(ICE_FLAG_LINK_LENIENT_MODE_ENA, pf->flags))
netdev_info(netdev, "The selected speed is not supported by the current media. Please select a link speed that is supported by the current media.\n");
err = -EOPNOTSUPP; goto done;
}
/* get our own copy of the bits to check against */
memset(&safe_ks, 0, sizeof(safe_ks));
safe_ks.base.cmd = copy_ks.base.cmd;
safe_ks.base.link_mode_masks_nwords =
copy_ks.base.link_mode_masks_nwords;
ice_get_link_ksettings(netdev, &safe_ks);
/* set autoneg back to what it currently is */
copy_ks.base.autoneg = safe_ks.base.autoneg; /* we don't compare the speed */
copy_ks.base.speed = safe_ks.base.speed;
while (test_and_set_bit(ICE_CFG_BUSY, pf->state)) {
timeout--; if (!timeout) {
err = -EBUSY; goto done;
}
usleep_range(TEST_SET_BITS_SLEEP_MIN, TEST_SET_BITS_SLEEP_MAX);
}
/* Copy the current user PHY configuration. The current user PHY * configuration is initialized during probe from PHY capabilities * software mode, and updated on set PHY configuration.
*/
config = pi->phy.curr_user_phy_cfg;
/* If speed didn't get set, set it to what it currently is. * This is needed because if advertise is 0 (as it is when autoneg * is disabled) then speed won't get set.
*/ if (!adv_link_speed)
adv_link_speed = curr_link_speed;
/* Convert the advertise link speeds to their corresponded PHY_TYPE */
ice_set_phy_type_from_speed(ks, &phy_type_low, &phy_type_high,
adv_link_speed);
if (!autoneg_changed && adv_link_speed == curr_link_speed) {
netdev_info(netdev, "Nothing changed, exiting without setting anything.\n"); goto done;
}
/* save the requested speeds */
pi->phy.link_info.req_speeds = adv_link_speed;
/* set link and auto negotiation so changes take effect */
config.caps |= ICE_AQ_PHY_ENA_LINK;
/* check if there is a PHY type for the requested advertised speed */ if (!(phy_type_low || phy_type_high)) {
netdev_info(netdev, "The selected speed is not supported by the current media. Please select a link speed that is supported by the current media.\n");
err = -EOPNOTSUPP; goto done;
}
/* intersect requested advertised speed PHY types with media PHY types * for set PHY configuration
*/
config.phy_type_high = cpu_to_le64(phy_type_high) &
phy_caps->phy_type_high;
config.phy_type_low = cpu_to_le64(phy_type_low) &
phy_caps->phy_type_low;
if (!(config.phy_type_high || config.phy_type_low)) { /* If there is no intersection and lenient mode is enabled, then * intersect the requested advertised speed with NVM media type * PHY types.
*/ if (test_bit(ICE_FLAG_LINK_LENIENT_MODE_ENA, pf->flags)) {
config.phy_type_high = cpu_to_le64(phy_type_high) &
pf->nvm_phy_type_hi;
config.phy_type_low = cpu_to_le64(phy_type_low) &
pf->nvm_phy_type_lo;
} else {
netdev_info(netdev, "The selected speed is not supported by the current media. Please select a link speed that is supported by the current media.\n");
err = -EOPNOTSUPP; goto done;
}
}
/* If link is up put link down */ if (pi->phy.link_info.link_info & ICE_AQ_LINK_UP) { /* Tell the OS link is going down, the link will go * back up when fw says it is ready asynchronously
*/
ice_print_link_msg(np->vsi, false);
netif_carrier_off(netdev);
netif_tx_stop_all_queues(netdev);
}
/* make the aq call */
err = ice_aq_set_phy_cfg(&pf->hw, pi, &config, NULL); if (err) {
netdev_info(netdev, "Set phy config failed,\n"); goto done;
}
if (nfc->data & RXH_IP_SRC || nfc->data & RXH_IP_DST) { switch (nfc->flow_type) { case TCP_V4_FLOW: case UDP_V4_FLOW: case SCTP_V4_FLOW: case GTPU_V4_FLOW: case GTPC_V4_FLOW: case GTPC_TEID_V4_FLOW: case GTPU_EH_V4_FLOW: case GTPU_UL_V4_FLOW: case GTPU_DL_V4_FLOW: if (nfc->data & RXH_IP_SRC)
hfld |= ICE_FLOW_HASH_FLD_IPV4_SA; if (nfc->data & RXH_IP_DST)
hfld |= ICE_FLOW_HASH_FLD_IPV4_DA; break; case TCP_V6_FLOW: case UDP_V6_FLOW: case SCTP_V6_FLOW: case GTPU_V6_FLOW: case GTPC_V6_FLOW: case GTPC_TEID_V6_FLOW: case GTPU_EH_V6_FLOW: case GTPU_UL_V6_FLOW: case GTPU_DL_V6_FLOW: if (nfc->data & RXH_IP_SRC)
hfld |= ICE_FLOW_HASH_FLD_IPV6_SA; if (nfc->data & RXH_IP_DST)
hfld |= ICE_FLOW_HASH_FLD_IPV6_DA; break; default: break;
}
}
if (nfc->data & RXH_L4_B_0_1 || nfc->data & RXH_L4_B_2_3) { switch (nfc->flow_type) { case TCP_V4_FLOW: case TCP_V6_FLOW: if (nfc->data & RXH_L4_B_0_1)
hfld |= ICE_FLOW_HASH_FLD_TCP_SRC_PORT; if (nfc->data & RXH_L4_B_2_3)
hfld |= ICE_FLOW_HASH_FLD_TCP_DST_PORT; break; case UDP_V4_FLOW: case UDP_V6_FLOW: if (nfc->data & RXH_L4_B_0_1)
hfld |= ICE_FLOW_HASH_FLD_UDP_SRC_PORT; if (nfc->data & RXH_L4_B_2_3)
hfld |= ICE_FLOW_HASH_FLD_UDP_DST_PORT; break; case SCTP_V4_FLOW: case SCTP_V6_FLOW: if (nfc->data & RXH_L4_B_0_1)
hfld |= ICE_FLOW_HASH_FLD_SCTP_SRC_PORT; if (nfc->data & RXH_L4_B_2_3)
hfld |= ICE_FLOW_HASH_FLD_SCTP_DST_PORT; break; default: break;
}
}
if (nfc->data & RXH_GTP_TEID) { switch (nfc->flow_type) { case GTPC_TEID_V4_FLOW: case GTPC_TEID_V6_FLOW:
hfld |= ICE_FLOW_HASH_FLD_GTPC_TEID; break; case GTPU_V4_FLOW: case GTPU_V6_FLOW:
hfld |= ICE_FLOW_HASH_FLD_GTPU_IP_TEID; break; case GTPU_EH_V4_FLOW: case GTPU_EH_V6_FLOW:
hfld |= ICE_FLOW_HASH_FLD_GTPU_EH_TEID; break; case GTPU_UL_V4_FLOW: case GTPU_UL_V6_FLOW:
hfld |= ICE_FLOW_HASH_FLD_GTPU_UP_TEID; break; case GTPU_DL_V4_FLOW: case GTPU_DL_V6_FLOW:
hfld |= ICE_FLOW_HASH_FLD_GTPU_DWN_TEID; break; default: break;
}
}
dev = ice_pf_to_dev(pf); if (ice_is_safe_mode(pf)) {
dev_dbg(dev, "Advanced RSS disabled. Package download failed, vsi num = %d\n",
vsi->vsi_num); return -EINVAL;
}
symm = !!(vsi->rss_hfunc == ICE_AQ_VSI_Q_OPT_RSS_HASH_SYM_TPLZ);
hashed_flds = ice_parse_hash_flds(nfc, symm); if (hashed_flds == ICE_HASH_INVALID) {
dev_dbg(dev, "Invalid hash fields, vsi num = %d\n",
vsi->vsi_num); return -EINVAL;
}
hdrs = ice_parse_hdrs(nfc); if (hdrs == ICE_FLOW_SEG_HDR_NONE) {
dev_dbg(dev, "Header type is not valid, vsi num = %d\n",
vsi->vsi_num); return -EINVAL;
}
nfc->data = 0; if (ice_is_safe_mode(pf)) {
dev_dbg(dev, "Advanced RSS disabled. Package download failed, vsi num = %d\n",
vsi->vsi_num); return 0;
}
hdrs = ice_parse_hdrs(nfc); if (hdrs == ICE_FLOW_SEG_HDR_NONE) {
dev_dbg(dev, "Header type is not valid, vsi num = %d\n",
vsi->vsi_num); return 0;
}
hash_flds = ice_get_rss_cfg(&pf->hw, vsi->idx, hdrs, &symm); if (hash_flds == ICE_HASH_INVALID) {
dev_dbg(dev, "No hash fields found for the given header type, vsi num = %d\n",
vsi->vsi_num); return 0;
}
if (ring->tx_pending > ICE_MAX_NUM_DESC ||
ring->tx_pending < ICE_MIN_NUM_DESC ||
ring->rx_pending > ICE_MAX_NUM_DESC ||
ring->rx_pending < ICE_MIN_NUM_DESC) {
netdev_err(netdev, "Descriptors requested (Tx: %d / Rx: %d) out of range [%d-%d] (increment %d)\n",
ring->tx_pending, ring->rx_pending,
ICE_MIN_NUM_DESC, ICE_MAX_NUM_DESC,
ICE_REQ_DESC_MULTIPLE); return -EINVAL;
}
/* Return if there is no rings (device is reloading) */ if (!vsi->tx_rings || !vsi->rx_rings) return -EBUSY;
new_tx_cnt = ALIGN(ring->tx_pending, ICE_REQ_DESC_MULTIPLE); if (new_tx_cnt != ring->tx_pending)
netdev_info(netdev, "Requested Tx descriptor count rounded up to %d\n",
new_tx_cnt);
new_rx_cnt = ALIGN(ring->rx_pending, ICE_REQ_DESC_MULTIPLE); if (new_rx_cnt != ring->rx_pending)
netdev_info(netdev, "Requested Rx descriptor count rounded up to %d\n",
new_rx_cnt);
/* if nothing to do return success */ if (new_tx_cnt == vsi->tx_rings[0]->count &&
new_rx_cnt == vsi->rx_rings[0]->count) {
netdev_dbg(netdev, "Nothing to change, descriptor count is same as requested\n"); return 0;
}
/* If there is a AF_XDP UMEM attached to any of Rx rings, * disallow changing the number of descriptors -- regardless * if the netdev is running or not.
*/ if (ice_xsk_any_rx_ring_ena(vsi)) return -EBUSY;
while (test_and_set_bit(ICE_CFG_BUSY, pf->state)) {
timeout--; if (!timeout) return -EBUSY;
usleep_range(1000, 2000);
}
/* set for the next time the netdev is started */ if (!netif_running(vsi->netdev)) {
ice_for_each_alloc_txq(vsi, i)
vsi->tx_rings[i]->count = new_tx_cnt;
ice_for_each_alloc_rxq(vsi, i)
vsi->rx_rings[i]->count = new_rx_cnt; if (ice_is_xdp_ena_vsi(vsi))
ice_for_each_xdp_txq(vsi, i)
vsi->xdp_rings[i]->count = new_tx_cnt;
vsi->num_tx_desc = (u16)new_tx_cnt;
vsi->num_rx_desc = (u16)new_rx_cnt;
netdev_dbg(netdev, "Link is down, descriptor count change happens when link is brought up\n"); goto done;
}
if (new_tx_cnt == vsi->tx_rings[0]->count) goto process_rx;
/* alloc updated Tx resources */
netdev_info(netdev, "Changing Tx descriptor count from %d to %d\n",
vsi->tx_rings[0]->count, new_tx_cnt);
ice_for_each_rxq(vsi, i) { /* clone ring and setup updated count */
rx_rings[i] = *vsi->rx_rings[i];
rx_rings[i].count = new_rx_cnt;
rx_rings[i].cached_phctime = pf->ptp.cached_phc_time;
rx_rings[i].desc = NULL;
rx_rings[i].rx_buf = NULL; /* this is to allow wr32 to have something to write to * during early allocation of Rx buffers
*/
rx_rings[i].tail = vsi->back->hw.hw_addr + PRTGEN_STATUS;
err = ice_setup_rx_ring(&rx_rings[i]); if (err) goto rx_unwind;
process_link: /* Bring interface down, copy in the new ring info, then restore the * interface. if VSI is up, bring it down and then back up
*/ if (!test_and_set_bit(ICE_VSI_DOWN, vsi->state)) {
ice_down(vsi);
if (rx_rings) {
ice_for_each_rxq(vsi, i) {
ice_free_rx_ring(vsi->rx_rings[i]); /* copy the real tail offset */
rx_rings[i].tail = vsi->rx_rings[i]->tail; /* this is to fake out the allocation routine * into thinking it has to realloc everything * but the recycling logic will let us re-use * the buffers allocated above
*/
rx_rings[i].next_to_use = 0;
rx_rings[i].next_to_clean = 0;
rx_rings[i].next_to_alloc = 0;
*vsi->rx_rings[i] = rx_rings[i];
}
kfree(rx_rings);
}
/** * ice_get_pauseparam - Get Flow Control status * @netdev: network interface device structure * @pause: ethernet pause (flow control) parameters * * Get requested flow control status from PHY capability. * If autoneg is true, then ethtool will send the ETHTOOL_GSET ioctl which * is handled by ice_get_link_ksettings. ice_get_link_ksettings will report * the negotiated Rx/Tx pause via lp_advertising.
*/ staticvoid
ice_get_pauseparam(struct net_device *netdev, struct ethtool_pauseparam *pause)
{ struct ice_netdev_priv *np = netdev_priv(netdev); struct ice_port_info *pi = np->vsi->port_info; struct ice_aqc_get_phy_caps_data *pcaps; struct ice_dcbx_cfg *dcbx_cfg; int status;
/* Changing the port's flow control is not supported if this isn't the * PF VSI
*/ if (vsi->type != ICE_VSI_PF) {
netdev_info(netdev, "Changing flow control parameters only supported for PF VSI\n"); return -EOPNOTSUPP;
}
/* Get pause param reports configured and negotiated flow control pause * when ETHTOOL_GLINKSETTINGS is defined. Since ETHTOOL_GLINKSETTINGS is * defined get pause param pause->autoneg reports SW configured setting, * so compare pause->autoneg with SW configured to prevent the user from * using set pause param to chance autoneg.
*/
pcaps = kzalloc(sizeof(*pcaps), GFP_KERNEL); if (!pcaps) return -ENOMEM;
/* Get current PHY config */
err = ice_aq_get_phy_caps(pi, false, ICE_AQC_REPORT_ACTIVE_CFG, pcaps,
NULL); if (err) {
kfree(pcaps); return err;
}
/* If we have link and don't have autoneg */ if (!test_bit(ICE_DOWN, pf->state) &&
!(hw_link_info->an_info & ICE_AQ_AN_COMPLETED)) { /* Send message that it might not necessarily work*/
netdev_info(netdev, "Autoneg did not complete so changing settings may not result in an actual change.\n");
}
if (dcbx_cfg->pfc.pfcena) {
netdev_info(netdev, "Priority flow control enabled. Cannot set link flow control.\n"); return -EOPNOTSUPP;
} if (pause->rx_pause && pause->tx_pause)
pi->fc.req_mode = ICE_FC_FULL; elseif (pause->rx_pause && !pause->tx_pause)
pi->fc.req_mode = ICE_FC_RX_PAUSE; elseif (!pause->rx_pause && pause->tx_pause)
pi->fc.req_mode = ICE_FC_TX_PAUSE; elseif (!pause->rx_pause && !pause->tx_pause)
pi->fc.req_mode = ICE_FC_NONE; else return -EINVAL;
/* Set the FC mode and only restart AN if link is up */
err = ice_set_fc(pi, &aq_failures, link_up);
if (aq_failures & ICE_SET_FC_AQ_FAIL_GET) {
netdev_info(netdev, "Set fc failed on the get_phy_capabilities call with err %d aq_err %s\n",
err, libie_aq_str(hw->adminq.sq_last_status));
err = -EAGAIN;
} elseif (aq_failures & ICE_SET_FC_AQ_FAIL_SET) {
netdev_info(netdev, "Set fc failed on the set_phy_config call with err %d aq_err %s\n",
err, libie_aq_str(hw->adminq.sq_last_status));
err = -EAGAIN;
} elseif (aq_failures & ICE_SET_FC_AQ_FAIL_UPDATE) {
netdev_info(netdev, "Set fc failed on the get_link_info call with err %d aq_err %s\n",
err, libie_aq_str(hw->adminq.sq_last_status));
err = -EAGAIN;
}
rxfh->hfunc = ETH_RSS_HASH_TOP; if (vsi->rss_hfunc == ICE_AQ_VSI_Q_OPT_RSS_HASH_SYM_TPLZ)
rxfh->input_xfrm |= RXH_XFRM_SYM_XOR;
if (!rxfh->indir) return 0;
lut = kzalloc(vsi->rss_table_size, GFP_KERNEL); if (!lut) return -ENOMEM;
err = ice_get_rss_key(vsi, rxfh->key); if (err) goto out;
err = ice_get_rss_lut(vsi, lut, vsi->rss_table_size); if (err) goto out;
if (ice_is_adq_active(pf)) { for (i = 0; i < vsi->rss_table_size; i++)
rxfh->indir[i] = offset + lut[i] % qcount; goto out;
}
for (i = 0; i < vsi->rss_table_size; i++)
rxfh->indir[i] = lut[i];
out:
kfree(lut); return err;
}
/** * ice_set_rxfh - set the Rx flow hash indirection table * @netdev: network interface device structure * @rxfh: pointer to param struct (indir, key, hfunc) * @extack: extended ACK from the Netlink message * * Returns -EINVAL if the table specifies an invalid queue ID, otherwise * returns 0 after programming the table.
*/ staticint
ice_set_rxfh(struct net_device *netdev, struct ethtool_rxfh_param *rxfh, struct netlink_ext_ack *extack)
{ struct ice_netdev_priv *np = netdev_priv(netdev);
u8 hfunc = ICE_AQ_VSI_Q_OPT_RSS_HASH_TPLZ; struct ice_vsi *vsi = np->vsi; struct ice_pf *pf = vsi->back; struct device *dev; int err;
dev = ice_pf_to_dev(pf); if (rxfh->hfunc != ETH_RSS_HASH_NO_CHANGE &&
rxfh->hfunc != ETH_RSS_HASH_TOP) return -EOPNOTSUPP;
if (!test_bit(ICE_FLAG_RSS_ENA, pf->flags)) { /* RSS not supported return error here */
netdev_warn(netdev, "RSS is not configured on this VSI!\n"); return -EIO;
}
if (ice_is_adq_active(pf)) {
netdev_err(netdev, "Cannot change RSS params with ADQ configured.\n"); return -EOPNOTSUPP;
}
/* Update the VSI's hash function */ if (rxfh->input_xfrm & RXH_XFRM_SYM_XOR)
hfunc = ICE_AQ_VSI_Q_OPT_RSS_HASH_SYM_TPLZ;
err = ice_set_rss_hfunc(vsi, hfunc); if (err) return err;
if (rxfh->key) { if (!vsi->rss_hkey_user) {
vsi->rss_hkey_user =
devm_kzalloc(dev, ICE_VSIQF_HKEY_ARRAY_SIZE,
GFP_KERNEL); if (!vsi->rss_hkey_user) return -ENOMEM;
}
memcpy(vsi->rss_hkey_user, rxfh->key,
ICE_VSIQF_HKEY_ARRAY_SIZE);
err = ice_set_rss_key(vsi, vsi->rss_hkey_user); if (err) return err;
}
if (!vsi->rss_lut_user) {
vsi->rss_lut_user = devm_kzalloc(dev, vsi->rss_table_size,
GFP_KERNEL); if (!vsi->rss_lut_user) return -ENOMEM;
}
/* Each 32 bits pointed by 'indir' is stored with a lut entry */ if (rxfh->indir) { int i;
for (i = 0; i < vsi->rss_table_size; i++)
vsi->rss_lut_user[i] = (u8)(rxfh->indir[i]);
} else {
ice_fill_rss_lut(vsi->rss_lut_user, vsi->rss_table_size,
vsi->rss_size);
}
err = ice_set_rss_lut(vsi, vsi->rss_lut_user, vsi->rss_table_size); if (err) return err;
/** * ice_get_max_txq - return the maximum number of Tx queues for in a PF * @pf: PF structure
*/ staticint ice_get_max_txq(struct ice_pf *pf)
{ return min(num_online_cpus(), pf->hw.func_caps.common_cap.num_txq);
}
/** * ice_get_max_rxq - return the maximum number of Rx queues for in a PF * @pf: PF structure
*/ staticint ice_get_max_rxq(struct ice_pf *pf)
{ return min(num_online_cpus(), pf->hw.func_caps.common_cap.num_rxq);
}
/** * ice_get_combined_cnt - return the current number of combined channels * @vsi: PF VSI pointer * * Go through all queue vectors and count ones that have both Rx and Tx ring * attached
*/ static u32 ice_get_combined_cnt(struct ice_vsi *vsi)
{
u32 combined = 0; int q_idx;
/** * ice_vsi_set_dflt_rss_lut - set default RSS LUT with requested RSS size * @vsi: VSI to reconfigure RSS LUT on * @req_rss_size: requested range of queue numbers for hashing * * Set the VSI's RSS parameters, configure the RSS LUT based on these.
*/ staticint ice_vsi_set_dflt_rss_lut(struct ice_vsi *vsi, int req_rss_size)
{ struct ice_pf *pf = vsi->back; struct device *dev; struct ice_hw *hw; int err;
u8 *lut;
dev = ice_pf_to_dev(pf);
hw = &pf->hw;
if (!req_rss_size) return -EINVAL;
lut = kzalloc(vsi->rss_table_size, GFP_KERNEL); if (!lut) return -ENOMEM;
/* set RSS LUT parameters */ if (!test_bit(ICE_FLAG_RSS_ENA, pf->flags))
vsi->rss_size = 1; else
vsi->rss_size = ice_get_valid_rss_size(hw, req_rss_size);
/** * ice_set_channels - set the number channels * @dev: network interface device structure * @ch: ethtool channel data structure
*/ staticint ice_set_channels(struct net_device *dev, struct ethtool_channels *ch)
{ struct ice_netdev_priv *np = netdev_priv(dev); struct ice_vsi *vsi = np->vsi; struct ice_pf *pf = vsi->back; int new_rx = 0, new_tx = 0; bool locked = false; int ret = 0;
/* do not support changing channels in Safe Mode */ if (ice_is_safe_mode(pf)) {
netdev_err(dev, "Changing channel in Safe Mode is not supported\n"); return -EOPNOTSUPP;
} /* do not support changing other_count */ if (ch->other_count != (test_bit(ICE_FLAG_FD_ENA, pf->flags) ? 1U : 0U)) return -EINVAL;
if (ice_is_adq_active(pf)) {
netdev_err(dev, "Cannot set channels with ADQ configured.\n"); return -EOPNOTSUPP;
}
if (test_bit(ICE_FLAG_FD_ENA, pf->flags) && pf->hw.fdir_active_fltr) {
netdev_err(dev, "Cannot set channels when Flow Director filters are active\n"); return -EOPNOTSUPP;
}
if (ch->rx_count && ch->tx_count) {
netdev_err(dev, "Dedicated RX or TX channels cannot be used simultaneously\n"); return -EINVAL;
}
if (new_rx < vsi->tc_cfg.numtc) {
netdev_err(dev, "Cannot set less Rx channels, than Traffic Classes you have (%u)\n",
vsi->tc_cfg.numtc); return -EINVAL;
} if (new_tx < vsi->tc_cfg.numtc) {
netdev_err(dev, "Cannot set less Tx channels, than Traffic Classes you have (%u)\n",
vsi->tc_cfg.numtc); return -EINVAL;
} if (new_rx > ice_get_max_rxq(pf)) {
netdev_err(dev, "Maximum allowed Rx channels is %d\n",
ice_get_max_rxq(pf)); return -EINVAL;
} if (new_tx > ice_get_max_txq(pf)) {
netdev_err(dev, "Maximum allowed Tx channels is %d\n",
ice_get_max_txq(pf)); return -EINVAL;
}
if (pf->cdev_info && pf->cdev_info->adev) {
mutex_lock(&pf->adev_mutex);
device_lock(&pf->cdev_info->adev->dev);
locked = true; if (pf->cdev_info->adev->dev.driver) {
netdev_err(dev, "Cannot change channels when RDMA is active\n");
ret = -EBUSY; goto adev_unlock;
}
}
ice_vsi_recfg_qs(vsi, new_rx, new_tx, locked);
if (!netif_is_rxfh_configured(dev)) {
ret = ice_vsi_set_dflt_rss_lut(vsi, new_rx); goto adev_unlock;
}
/* Update rss_size due to change in Rx queues */
vsi->rss_size = ice_get_valid_rss_size(&pf->hw, new_rx);
adev_unlock: if (locked) {
device_unlock(&pf->cdev_info->adev->dev);
mutex_unlock(&pf->adev_mutex);
} return ret;
}
/** * ice_get_wol - get current Wake on LAN configuration * @netdev: network interface device structure * @wol: Ethtool structure to retrieve WoL settings
*/ staticvoid ice_get_wol(struct net_device *netdev, struct ethtool_wolinfo *wol)
{ struct ice_netdev_priv *np = netdev_priv(netdev); struct ice_pf *pf = np->vsi->back;
if (np->vsi->type != ICE_VSI_PF)
netdev_warn(netdev, "Wake on LAN is not supported on this interface!\n");
/* Get WoL settings based on the HW capability */ if (ice_is_wol_supported(&pf->hw)) {
wol->supported = WAKE_MAGIC;
wol->wolopts = pf->wol_ena ? WAKE_MAGIC : 0;
} else {
wol->supported = 0;
wol->wolopts = 0;
}
}
/** * ice_set_wol - set Wake on LAN on supported device * @netdev: network interface device structure * @wol: Ethtool structure to set WoL
*/ staticint ice_set_wol(struct net_device *netdev, struct ethtool_wolinfo *wol)
{ struct ice_netdev_priv *np = netdev_priv(netdev); struct ice_vsi *vsi = np->vsi; struct ice_pf *pf = vsi->back;
if (vsi->type != ICE_VSI_PF || !ice_is_wol_supported(&pf->hw)) return -EOPNOTSUPP;
/* only magic packet is supported */ if (wol->wolopts && wol->wolopts != WAKE_MAGIC) return -EOPNOTSUPP;
/* Set WoL only if there is a new value */ if (pf->wol_ena != !!wol->wolopts) {
pf->wol_ena = !!wol->wolopts;
device_set_wakeup_enable(ice_pf_to_dev(pf), pf->wol_ena);
netdev_dbg(netdev, "WoL magic packet %sabled\n",
pf->wol_ena ? "en" : "dis");
}
return 0;
}
/** * ice_get_rc_coalesce - get ITR values for specific ring container * @ec: ethtool structure to fill with driver's coalesce settings * @rc: ring container that the ITR values will come from * * Query the device for ice_ring_container specific ITR values. This is * done per ice_ring_container because each q_vector can have 1 or more rings * and all of said ring(s) will have the same ITR values. * * Returns 0 on success, negative otherwise.
*/ staticint
ice_get_rc_coalesce(struct ethtool_coalesce *ec, struct ice_ring_container *rc)
{ if (!rc->rx_ring) return -EINVAL;
/** * ice_get_q_coalesce - get a queue's ITR/INTRL (coalesce) settings * @vsi: VSI associated to the queue for getting ITR/INTRL (coalesce) settings * @ec: coalesce settings to program the device with * @q_num: update ITR/INTRL (coalesce) settings for this queue number/index * * Return 0 on success, and negative under the following conditions: * 1. Getting Tx or Rx ITR/INTRL (coalesce) settings failed. * 2. The q_num passed in is not a valid number/index for Tx and Rx rings.
*/ staticint
ice_get_q_coalesce(struct ice_vsi *vsi, struct ethtool_coalesce *ec, int q_num)
{ if (q_num < vsi->num_rxq && q_num < vsi->num_txq) { if (ice_get_rc_coalesce(ec,
&vsi->rx_rings[q_num]->q_vector->rx)) return -EINVAL; if (ice_get_rc_coalesce(ec,
&vsi->tx_rings[q_num]->q_vector->tx)) return -EINVAL;
} elseif (q_num < vsi->num_rxq) { if (ice_get_rc_coalesce(ec,
&vsi->rx_rings[q_num]->q_vector->rx)) return -EINVAL;
} elseif (q_num < vsi->num_txq) { if (ice_get_rc_coalesce(ec,
&vsi->tx_rings[q_num]->q_vector->tx)) return -EINVAL;
} else { return -EINVAL;
}
return 0;
}
/** * __ice_get_coalesce - get ITR/INTRL values for the device * @netdev: pointer to the netdev associated with this query * @ec: ethtool structure to fill with driver's coalesce settings * @q_num: queue number to get the coalesce settings for * * If the caller passes in a negative q_num then we return coalesce settings * based on queue number 0, else use the actual q_num passed in.
*/ staticint
__ice_get_coalesce(struct net_device *netdev, struct ethtool_coalesce *ec, int q_num)
{ struct ice_netdev_priv *np = netdev_priv(netdev); struct ice_vsi *vsi = np->vsi;
if (q_num < 0)
q_num = 0;
if (ice_get_q_coalesce(vsi, ec, q_num)) return -EINVAL;
/** * ice_set_rc_coalesce - set ITR values for specific ring container * @ec: ethtool structure from user to update ITR settings * @rc: ring container that the ITR values will come from * @vsi: VSI associated to the ring container * * Set specific ITR values. This is done per ice_ring_container because each * q_vector can have 1 or more rings and all of said ring(s) will have the same * ITR values. * * Returns 0 on success, negative otherwise.
*/ staticint
ice_set_rc_coalesce(struct ethtool_coalesce *ec, struct ice_ring_container *rc, struct ice_vsi *vsi)
{ constchar *c_type_str = (rc->type == ICE_RX_CONTAINER) ? "rx" : "tx";
u32 use_adaptive_coalesce, coalesce_usecs; struct ice_pf *pf = vsi->back;
u16 itr_setting;
itr_setting = rc->itr_setting; if (coalesce_usecs != itr_setting && use_adaptive_coalesce) {
netdev_info(vsi->netdev, "%s interrupt throttling cannot be changed if adaptive-%s is enabled\n",
c_type_str, c_type_str); return -EINVAL;
}
if (coalesce_usecs > ICE_ITR_MAX) {
netdev_info(vsi->netdev, "Invalid value, %s-usecs range is 0-%d\n",
c_type_str, ICE_ITR_MAX); return -EINVAL;
}
if (use_adaptive_coalesce) {
rc->itr_mode = ITR_DYNAMIC;
} else {
rc->itr_mode = ITR_STATIC; /* store user facing value how it was set */
rc->itr_setting = coalesce_usecs; /* write the change to the register */
ice_write_itr(rc, coalesce_usecs); /* force writes to take effect immediately, the flush shouldn't * be done in the functions above because the intent is for * them to do lazy writes.
*/
ice_flush(&pf->hw);
}
return 0;
}
/** * ice_set_q_coalesce - set a queue's ITR/INTRL (coalesce) settings * @vsi: VSI associated to the queue that need updating * @ec: coalesce settings to program the device with * @q_num: update ITR/INTRL (coalesce) settings for this queue number/index * * Return 0 on success, and negative under the following conditions: * 1. Setting Tx or Rx ITR/INTRL (coalesce) settings failed. * 2. The q_num passed in is not a valid number/index for Tx and Rx rings.
*/ staticint
ice_set_q_coalesce(struct ice_vsi *vsi, struct ethtool_coalesce *ec, int q_num)
{ if (q_num < vsi->num_rxq && q_num < vsi->num_txq) { if (ice_set_rc_coalesce(ec,
&vsi->rx_rings[q_num]->q_vector->rx,
vsi)) return -EINVAL;
/** * ice_print_if_odd_usecs - print message if user tries to set odd [tx|rx]-usecs * @netdev: netdev used for print * @itr_setting: previous user setting * @use_adaptive_coalesce: if adaptive coalesce is enabled or being enabled * @coalesce_usecs: requested value of [tx|rx]-usecs * @c_type_str: either "rx" or "tx" to match user set field of [tx|rx]-usecs
*/ staticvoid
ice_print_if_odd_usecs(struct net_device *netdev, u16 itr_setting,
u32 use_adaptive_coalesce, u32 coalesce_usecs, constchar *c_type_str)
{ if (use_adaptive_coalesce) return;
if (itr_setting != coalesce_usecs && (coalesce_usecs % 2))
netdev_info(netdev, "User set %s-usecs to %d, device only supports even values. Rounding down and attempting to set %s-usecs to %d\n",
c_type_str, coalesce_usecs, c_type_str,
ITR_REG_ALIGN(coalesce_usecs));
}
/** * __ice_set_coalesce - set ITR/INTRL values for the device * @netdev: pointer to the netdev associated with this query * @ec: ethtool structure to fill with driver's coalesce settings * @q_num: queue number to get the coalesce settings for * * If the caller passes in a negative q_num then we set the coalesce settings * for all Tx/Rx queues, else use the actual q_num passed in.
*/ staticint
__ice_set_coalesce(struct net_device *netdev, struct ethtool_coalesce *ec, int q_num)
{ struct ice_netdev_priv *np = netdev_priv(netdev); struct ice_vsi *vsi = np->vsi;
if (q_num < 0) { struct ice_q_vector *q_vector = vsi->q_vectors[0]; int v_idx;
if (q_vector) {
ice_print_if_odd_usecs(netdev, q_vector->rx.itr_setting,
ec->use_adaptive_rx_coalesce,
ec->rx_coalesce_usecs, "rx");
ice_for_each_q_vector(vsi, v_idx) { /* In some cases if DCB is configured the num_[rx|tx]q * can be less than vsi->num_q_vectors. This check * accounts for that so we don't report a false failure
*/ if (v_idx >= vsi->num_rxq && v_idx >= vsi->num_txq) goto set_complete;
if (ice_set_q_coalesce(vsi, ec, v_idx)) return -EINVAL;
status = ice_aq_sff_eeprom(hw, 0, addr, offset, page, 0, value, 1, 0,
NULL); if (status) return status;
if (value[0] == ICE_MODULE_TYPE_SFP)
is_sfp = true;
memset(data, 0, ee->len); for (i = 0; i < ee->len; i += SFF_READ_BLOCK_SIZE) {
offset = i + ee->offset;
page = 0;
/* Check if we need to access the other memory page */ if (is_sfp) { if (offset >= ETH_MODULE_SFF_8079_LEN) {
offset -= ETH_MODULE_SFF_8079_LEN;
addr = ICE_I2C_EEPROM_DEV_ADDR2;
}
} else { while (offset >= ETH_MODULE_SFF_8436_LEN) { /* Compute memory page number and offset. */
offset -= ETH_MODULE_SFF_8436_LEN / 2;
page++;
}
}
/* Bit 2 of EEPROM address 0x02 declares upper * pages are disabled on QSFP modules. * SFP modules only ever use page 0.
*/ if (page == 0 || !(data[0x2] & 0x4)) {
u32 copy_len;
/* If i2c bus is busy due to slow page change or * link management access, call can fail. This is normal. * So we retry this a few times.
*/ for (j = 0; j < 4; j++) {
status = ice_aq_sff_eeprom(hw, 0, addr, offset, page,
!is_sfp, value,
SFF_READ_BLOCK_SIZE,
0, NULL);
netdev_dbg(netdev, "SFF %02X %02X %02X %X = %02X%02X%02X%02X.%02X%02X%02X%02X (%X)\n",
addr, offset, page, is_sfp,
value[0], value[1], value[2], value[3],
value[4], value[5], value[6], value[7],
status); if (status) {
usleep_range(1500, 2500);
memset(value, 0, SFF_READ_BLOCK_SIZE); continue;
} break;
}
/* Make sure we have enough room for the new block */
copy_len = min_t(u32, SFF_READ_BLOCK_SIZE, ee->len - i);
memcpy(data + i, value, copy_len);
}
} return 0;
}
/** * ice_get_port_fec_stats - returns FEC correctable, uncorrectable stats per * pcsquad, pcsport * @hw: pointer to the HW struct * @pcs_quad: pcsquad for input port * @pcs_port: pcsport for input port * @fec_stats: buffer to hold FEC statistics for given port * * Return: 0 on success, negative on failure.
*/ staticint ice_get_port_fec_stats(struct ice_hw *hw, u16 pcs_quad, u16 pcs_port, struct ethtool_fec_stats *fec_stats)
{
u32 fec_uncorr_low_val = 0, fec_uncorr_high_val = 0;
u32 fec_corr_low_val = 0, fec_corr_high_val = 0; int err;
/** * ice_ethtool_reset - triggers a given type of reset * @dev: network interface device structure * @flags: set of reset flags * * Return: 0 on success, -EOPNOTSUPP when using unsupported set of flags.
*/ staticint ice_ethtool_reset(struct net_device *dev, u32 *flags)
{ struct ice_netdev_priv *np = netdev_priv(dev); struct ice_pf *pf = np->vsi->back; enum ice_reset_req reset;
switch (*flags) { case ICE_ETHTOOL_CORER:
reset = ICE_RESET_CORER; break; case ICE_ETHTOOL_GLOBR:
reset = ICE_RESET_GLOBR; break; case ICE_ETHTOOL_PFR:
reset = ICE_RESET_PFR; break; default:
netdev_info(dev, "Unsupported set of ethtool flags"); return -EOPNOTSUPP;
}
ice_schedule_reset(pf, reset);
*flags = 0;
return 0;
}
/** * ice_repr_ethtool_reset - triggers a VF reset * @dev: network interface device structure * @flags: set of reset flags * * Return: 0 on success, * -EOPNOTSUPP when using unsupported set of flags * -EBUSY when VF is not ready for reset.
*/ staticint ice_repr_ethtool_reset(struct net_device *dev, u32 *flags)
{ struct ice_repr *repr = ice_netdev_to_repr(dev); struct ice_vf *vf;
if (repr->type != ICE_REPR_TYPE_VF ||
*flags != ICE_ETHTOOL_VFR) return -EOPNOTSUPP;
vf = repr->vf;
if (ice_check_vf_ready_for_cfg(vf)) return -EBUSY;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.