ioctl的整個流程,以ethtool為例
阿新 • • 發佈:2021-01-26
相關的ethtool命令:
ethtool -s|--change DEVNAME Change generic options
[ speed %d ]
[ duplex half|full ]
[ port tp|aui|bnc|mii|fibre ]
[ mdix auto|on|off ]
[ autoneg on|off ]
[ advertise %x ]
[ phyad %d ]
[ xcvr internal|external ]
[ wol p|u|m|b|a|g|s|d... ]
[ sopass %x:%x:%x:%x:%x:%x ]
該命令可以設定網絡卡的速率雙工等等引數,我實際用到的命令是ethtool -s ens1f1 speed 1000,即將ens1f1網口的速率設定為 GE,下面我們來看看配置是怎麼下到網絡卡上的:
-
ethtool側: do_sset --> ecmd.cmd = ETHTOOL_SSET; send_ioctl --> ioctl(ctx->fd, SIOCETHTOOL, &ctx->ifr);標紅的是命令字,核心是根據命令字做分發處理的。
-
linux側:
第一步:dev_ioctl根據 SIOCETHTOOL
case SIOCETHTOOL:
dev_load(net, ifr.ifr_name);
rtnl_lock();
ret = dev_ethtool(net, &ifr);
rtnl_unlock();
if (!ret) {
if (colon)
*colon = ':';
if (copy_to_user(arg, &ifr,
sizeof(struct ifreq)))
ret = -EFAULT;
}
return ret;
第二步:dev_ethtool 根據ETHTOOL_SSET
case ETHTOOL_SSET:
rc = ethtool_set_settings(dev, useraddr);
break;
第三步:ethtool_set_settings
static int ethtool_set_settings(struct net_device *dev, void __user *useraddr)
{
struct ethtool_cmd cmd;
ASSERT_RTNL();
if (copy_from_user(&cmd, useraddr, sizeof(cmd)))
return -EFAULT;
/* first, try new %ethtool_link_ksettings API. */
if (dev->ethtool_ops->set_link_ksettings) {
struct ethtool_link_ksettings link_ksettings;
if (!convert_legacy_settings_to_link_ksettings(&link_ksettings,
&cmd))
return -EINVAL;
link_ksettings.base.cmd = ETHTOOL_SLINKSETTINGS;
link_ksettings.base.link_mode_masks_nwords
= __ETHTOOL_LINK_MODE_MASK_NU32;
return dev->ethtool_ops->set_link_ksettings(dev,
&link_ksettings);
}
/* legacy %ethtool_cmd API */
/* TODO: return -EOPNOTSUPP when ethtool_ops::get_settings
* disappears internally
*/
if (!dev->ethtool_ops->set_settings)
return -EOPNOTSUPP;
return dev->ethtool_ops->set_settings(dev, &cmd);
}
最後呼叫到 dev->ethtool_ops->set_link_ksettings(dev, &link_ksettings); 這個是網絡卡初始化時掛的ops指標
#ifdef ETHTOOL_GLINKSETTINGS
.get_link_ksettings = i40e_get_link_ksettings,
.set_link_ksettings = i40e_set_link_ksettings,
#endif /* ETHTOOL_GLINKSETTINGS */
我用的是X710網絡卡,下面我們看看i40e的原始碼是如何處理的:
static int i40e_set_link_ksettings(struct net_device *netdev,
const struct ethtool_link_ksettings *ks)
{
struct i40e_netdev_priv *np = netdev_priv(netdev);
struct i40e_aq_get_phy_abilities_resp abilities;
struct ethtool_link_ksettings safe_ks;
struct ethtool_link_ksettings copy_ks;
struct i40e_aq_set_phy_config config;
struct i40e_pf *pf = np->vsi->back;
struct i40e_vsi *vsi = np->vsi;
struct i40e_hw *hw = &pf->hw;
bool autoneg_changed = false;
i40e_status status = 0;
int timeout = 50;
int err = 0;
u8 autoneg;
/* Changing port settings is not supported if this isn't the
* port's controlling PF
*/
if (hw->partition_id != 1) {
i40e_partition_setting_complaint(pf);
return -EOPNOTSUPP;
}
if (vsi != pf->vsi[pf->lan_vsi])
return -EOPNOTSUPP;
if (hw->phy.media_type != I40E_MEDIA_TYPE_BASET &&
hw->phy.media_type != I40E_MEDIA_TYPE_FIBER &&
hw->phy.media_type != I40E_MEDIA_TYPE_BACKPLANE &&
hw->phy.media_type != I40E_MEDIA_TYPE_DA &&
hw->phy.link_info.link_info & I40E_AQ_LINK_UP)
return -EOPNOTSUPP;
if (hw->device_id == I40E_DEV_ID_KX_B ||
hw->device_id == I40E_DEV_ID_KX_C ||
hw->device_id == I40E_DEV_ID_20G_KR2 ||
hw->device_id == I40E_DEV_ID_20G_KR2_A ||
hw->device_id == I40E_DEV_ID_25G_B ||
hw->device_id == I40E_DEV_ID_KX_X722) {
netdev_info(netdev, "Changing settings is not supported on backplane.\n");
return -EOPNOTSUPP;
}
/* copy the ksettings to copy_ks to avoid modifying the origin */
memcpy(©_ks, ks, sizeof(struct ethtool_link_ksettings));
/* save autoneg out of ksettings */
autoneg = copy_ks.base.autoneg;
/* get our own copy of the bits to check against */
memset(&safe_ks, 0, sizeof(struct ethtool_link_ksettings));
safe_ks.base.cmd = copy_ks.base.cmd;
safe_ks.base.link_mode_masks_nwords =
copy_ks.base.link_mode_masks_nwords;
i40e_get_link_ksettings(netdev, &safe_ks);
/* Get link modes supported by hardware 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))
return -EINVAL;
/* set autoneg back to what it currently is */
copy_ks.base.autoneg = safe_ks.base.autoneg;
/* If copy_ks.base and safe_ks.base are not the same now, then they are
* trying to set something that we do not support.
*/
if (memcmp(©_ks.base, &safe_ks.base,
sizeof(struct ethtool_link_settings)))
return -EOPNOTSUPP;
while (test_and_set_bit(__I40E_CONFIG_BUSY, pf->state)) {
timeout--;
if (!timeout)
return -EBUSY;
usleep_range(1000, 2000);
}
/* Get the current phy config */
status = i40e_aq_get_phy_capabilities(hw, false, false, &abilities,
NULL);
if (status) {
err = -EAGAIN;
goto done;
}
/* Copy abilities to config in case autoneg is not
* set below
*/
memset(&config, 0, sizeof(struct i40e_aq_set_phy_config));
config.abilities = abilities.abilities;
/* Check autoneg */
if (autoneg == AUTONEG_ENABLE) {
/* If autoneg was not already enabled */
if (!(hw->phy.link_info.an_info & I40E_AQ_AN_COMPLETED)) {
/* If autoneg is not supported, return error */
if (!ethtool_link_ksettings_test_link_mode(
&safe_ks, supported, Autoneg)) {
netdev_info(netdev, "Autoneg not supported on this phy\n");
err = -EINVAL;
goto done;
}
/* Autoneg is allowed to change */
config.abilities = abilities.abilities |
I40E_AQ_PHY_ENABLE_AN;
autoneg_changed = true;
}
} else {
/* If autoneg is currently enabled */
if (hw->phy.link_info.an_info & I40E_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(
&safe_ks, supported, Autoneg) &&
hw->phy.link_info.phy_type !=
I40E_PHY_TYPE_10GBASE_T) {
netdev_info(netdev, "Autoneg cannot be disabled on this phy\n");
err = -EINVAL;
goto done;
}
/* Autoneg is allowed to change */
config.abilities = abilities.abilities &
~I40E_AQ_PHY_ENABLE_AN;
autoneg_changed = true;
}
}
if (ethtool_link_ksettings_test_link_mode(ks, advertising,
100baseT_Full))
config.link_speed |= I40E_LINK_SPEED_100MB;
if (ethtool_link_ksettings_test_link_mode(ks, advertising,
1000baseT_Full) ||
#ifdef HAVE_ETHTOOL_NEW_10G_BITS
ethtool_link_ksettings_test_link_mode(ks, advertising,
1000baseX_Full) ||
#endif
ethtool_link_ksettings_test_link_mode(ks, advertising,
1000baseKX_Full))
config.link_speed |= I40E_LINK_SPEED_1GB;
if (ethtool_link_ksettings_test_link_mode(ks, advertising,
10000baseT_Full) ||
ethtool_link_ksettings_test_link_mode(ks, advertising,
10000baseKX4_Full) ||
ethtool_link_ksettings_test_link_mode(ks, advertising,
10000baseKR_Full) ||
#ifdef HAVE_ETHTOOL_NEW_10G_BITS
ethtool_link_ksettings_test_link_mode(ks, advertising,
10000baseCR_Full) ||
ethtool_link_ksettings_test_link_mode(ks, advertising,
10000baseSR_Full) ||
ethtool_link_ksettings_test_link_mode(ks, advertising,
10000baseLR_Full))
#else
0)
#endif /* HAVE_ETHTOOL_NEW_10G_BITS */
config.link_speed |= I40E_LINK_SPEED_10GB;
#ifdef HAVE_ETHTOOL_NEW_2500MB_BITS
if (ethtool_link_ksettings_test_link_mode(ks, advertising,
2500baseT_Full))
config.link_speed |= I40E_LINK_SPEED_2_5GB;
#endif /* HAVE_ETHTOOL_NEW_2500MB_BITS */
#ifdef HAVE_ETHTOOL_5G_BITS
if (ethtool_link_ksettings_test_link_mode(ks, advertising,
5000baseT_Full))
config.link_speed |= I40E_LINK_SPEED_5GB;
#endif /* HAVE_ETHTOOL_5G_BITS */
if (ethtool_link_ksettings_test_link_mode(ks, advertising,
20000baseKR2_Full))
config.link_speed |= I40E_LINK_SPEED_20GB;
#ifdef HAVE_ETHTOOL_25G_BITS
if (ethtool_link_ksettings_test_link_mode(ks, advertising,
25000baseCR_Full) ||
ethtool_link_ksettings_test_link_mode(ks, advertising,
25000baseKR_Full) ||
ethtool_link_ksettings_test_link_mode(ks, advertising,
25000baseSR_Full))
config.link_speed |= I40E_LINK_SPEED_25GB;
#endif /* HAVE_ETHTOOL_25G_BITS */
if (ethtool_link_ksettings_test_link_mode(ks, advertising,
40000baseKR4_Full) ||
ethtool_link_ksettings_test_link_mode(ks, advertising,
40000baseCR4_Full) ||
ethtool_link_ksettings_test_link_mode(ks, advertising,
40000baseSR4_Full) ||
ethtool_link_ksettings_test_link_mode(ks, advertising,
40000baseLR4_Full))
config.link_speed |= I40E_LINK_SPEED_40GB;
/* 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 (!config.link_speed)
config.link_speed = abilities.link_speed;
if (autoneg_changed || (abilities.link_speed != config.link_speed)) {
/* copy over the rest of the abilities */
config.phy_type = abilities.phy_type;
config.phy_type_ext = abilities.phy_type_ext;
config.eee_capability = abilities.eee_capability;
config.eeer = abilities.eeer_val;
config.low_power_ctrl = abilities.d3_lpan;
config.fec_config = abilities.fec_cfg_curr_mod_ext_info &
I40E_AQ_PHY_FEC_CONFIG_MASK;
/* save the requested speeds */
hw->phy.link_info.requested_speeds = config.link_speed;
/* set link and auto negotiation so changes take effect */
config.abilities |= I40E_AQ_PHY_ENABLE_ATOMIC_LINK;
/* If link is up put link down */
if (hw->phy.link_info.link_info & I40E_AQ_LINK_UP) {
/* Tell the OS link is going down, the link will go
* back up when fw says it is ready asynchronously
*/
i40e_print_link_message(vsi, false);
netif_carrier_off(netdev);
netif_tx_stop_all_queues(netdev);
}
/* make the aq call */
status = i40e_aq_set_phy_config(hw, &config, NULL);
if (status) {
netdev_info(netdev,
"Set phy config failed, err %s aq_err %s\n",
i40e_stat_str(hw, status),
i40e_aq_str(hw, hw->aq.asq_last_status));
err = -EAGAIN;
goto done;
}
status = i40e_update_link_info(hw);
if (status)
netdev_dbg(netdev,
"Updating link info failed with err %s aq_err %s\n",
i40e_stat_str(hw, status),
i40e_aq_str(hw, hw->aq.asq_last_status));
} else {
netdev_info(netdev, "Nothing changed, exiting without setting anything.\n");
}
done:
clear_bit(__I40E_CONFIG_BUSY, pf->state);
return err;
}