使用 ethtool 工具可以查看网卡硬件的统计信息,比如,收包数、发包数、单播数、多播数等等。具体操作如下:
$ ethtool -S enp0s3f0
NIC statistics:
mmc_tx_octetcount_gb: 224841119
mmc_tx_framecount_gb: 1295134
mmc_tx_broadcastframe_g: 72032
mmc_tx_multicastframe_g: 448
mmc_tx_64_octets_gb: 76558
...
上面是 Gmac 网卡的输出信息,比较多,只截取了其中一部分。对于不同的网卡,上面输出的内容会有所不同,但基本格式是一样的,因为这是 ethtool 设定的输出格式,和具体的网卡无关。共分为两列,第一列是统计项的名称,第二列是对应的数值。之所以会有不同,是因为统计项的名称是网卡驱动给出的,ethtool 只负责显示。
在内核中,每块网卡都对应一个 struct net_device 结构体,该结构体包含一个 const struct ethtool_ops *ethtool_ops 成员,该结构体成员中包含一系列函数指针,由具体的网卡驱动实现,用于配合内核实现 ethtool 相关操作。对于 gmac 网卡而言,struct ethtool_ops 的赋值如下:
static const struct ethtool_ops stmmac_ethtool_ops = {
...
.get_ethtool_stats = stmmac_get_ethtool_stats,
.get_strings = stmmac_get_strings,
.get_wol = stmmac_get_wol,
.set_wol = stmmac_set_wol,
.get_eee = stmmac_ethtool_op_get_eee,
.set_eee = stmmac_ethtool_op_set_eee,
.get_sset_count = stmmac_get_sset_count,
...
};
在实现上,ethtool 只需要做三件事,并且都需要内核的配合。ethtool 通过 ioctl 与内核通信,ioctl 的 request 参数是 SIOCETHTOOL。
1)使用 ETHTOOL_GSSET_INFO 命令字请求内核,获得统计项的数量。在内核中会通过以下流程调用到 Gmac 驱动的 stmmac_get_sset_count() 函数:
ethtool_get_sset_info()
__ethtool_get_sset_count()
ops->get_sset_count()
stmmac_get_sset_count() 的实现如下:
static int stmmac_get_sset_count(struct net_device *netdev, int sset)
{
struct stmmac_priv *priv = netdev_priv(netdev);
int i, len, safety_len = 0;
switch (sset) {
case ETH_SS_STATS:
len = STMMAC_STATS_LEN;
if (priv->dma_cap.rmon)
len += STMMAC_MMC_STATS_LEN;
if (priv->dma_cap.asp) {
for (i = 0; i < STMMAC_SAFETY_FEAT_SIZE; i++) {
if (!stmmac_safety_feat_dump(priv,
&priv->sstats, i,
NULL, NULL))
safety_len++;
}
len += safety_len;
}
return len;
default:
return -EOPNOTSUPP;
}
}
2)使用 ETHTOOL_GSTRINGS 命令字请求内核,获得统计项的名称。在内核中会通过以下流程调用到 Gmac 驱动的 stmmac_get_strings() 函数:
ethtool_get_strings()
__ethtool_get_strings()
ops->get_strings()
stmmac_get_strings() 的实现如下:
static void stmmac_get_strings(struct net_device *dev, u32 stringset, u8 *data)
{
int i;
u8 *p = data;
struct stmmac_priv *priv = netdev_priv(dev);
switch (stringset) {
case ETH_SS_STATS:
if (priv->dma_cap.asp) {
for (i = 0; i < STMMAC_SAFETY_FEAT_SIZE; i++) {
const char *desc;
if (!stmmac_safety_feat_dump(priv,
&priv->sstats, i,
NULL, &desc)) {
memcpy(p, desc, ETH_GSTRING_LEN);
p += ETH_GSTRING_LEN;
}
}
}
if (priv->dma_cap.rmon)
for (i = 0; i < STMMAC_MMC_STATS_LEN; i++) {
memcpy(p, stmmac_mmc[i].stat_string,
ETH_GSTRING_LEN);
p += ETH_GSTRING_LEN;
}
for (i = 0; i < STMMAC_STATS_LEN; i++) {
memcpy(p, stmmac_gstrings_stats[i].stat_string,
ETH_GSTRING_LEN);
p += ETH_GSTRING_LEN;
}
break;
default:
WARN_ON(1);
break;
}
}
3)使用 ETHTOOL_GSTATS 命令字请求内核,获得统计项的值。在内核中会通过以下流程调用到 Gmac 驱动的 stmmac_get_ethtool_stats() 函数:
ethtool_get_stats()
ops->get_ethtool_stats()
stmmac_get_ethtool_stats() 的实现如下:
static void stmmac_get_ethtool_stats(struct net_device *dev,
struct ethtool_stats *dummy, u64 *data)
{
struct stmmac_priv *priv = netdev_priv(dev);
u32 rx_queues_count = priv->plat->rx_queues_to_use;
u32 tx_queues_count = priv->plat->tx_queues_to_use;
unsigned long count;
int i, j = 0, ret;
if (priv->dma_cap.asp) {
for (i = 0; i < STMMAC_SAFETY_FEAT_SIZE; i++) {
if (!stmmac_safety_feat_dump(priv, &priv->sstats, i,
&count, NULL))
data[j++] = count;
}
}
/* Update the DMA HW counters for dwmac10/100 */
ret = stmmac_dma_diagnostic_fr(priv, &dev->stats, (void *) &priv->xstats,
priv->ioaddr);
if (ret) {
/* If supported, for new GMAC chips expose the MMC counters */
if (priv->dma_cap.rmon) {
dwmac_mmc_read(priv->mmcaddr, &priv->mmc);
for (i = 0; i < STMMAC_MMC_STATS_LEN; i++) {
char *p;
p = (char *)priv + stmmac_mmc[i].stat_offset;
data[j++] = (stmmac_mmc[i].sizeof_stat ==
sizeof(u64)) ? (*(u64 *)p) :
(*(u32 *)p);
}
}
if (priv->eee_enabled) {
int val = phy_get_eee_err(dev->phydev);
if (val)
priv->xstats.phy_eee_wakeup_error_n = val;
}
if (priv->synopsys_id >= DWMAC_CORE_3_50)
stmmac_mac_debug(priv, priv->ioaddr,
(void *)&priv->xstats,
rx_queues_count, tx_queues_count);
}
for (i = 0; i < STMMAC_STATS_LEN; i++) {
char *p = (char *)priv + stmmac_gstrings_stats[i].stat_offset;
data[j++] = (stmmac_gstrings_stats[i].sizeof_stat ==
sizeof(u64)) ? (*(u64 *)p) : (*(u32 *)p);
}
}
上述实现中,通过 dwmac_mmc_read() 函数读取 Gmac 网卡 MMC 模块中寄存器的值。