内核网卡诊断与ethtool工具分析

以下以网讯网卡 ngbe 驱动为例

内核侧

ethtool_ops 结构体注释

  • include/linux/ethtool.h 中 ethtool_ops 结构体
struct ethtool_ops {
	/* 已弃用,请使用 get_link_ksettings/set_link_ksettings API。获取各种设备设置,包括以太网链路设置。
	   在调用 cmd 参数之前,应已清除get_settings参数。返回负错误代码或零。 */
	int	(*get_settings)(struct net_device *, struct ethtool_cmd *);
	int	(*set_settings)(struct net_device *, struct ethtool_cmd *);
	/* 报告驱动程序/设备信息。 应仅设置driver,version,fw_version和bus_info字段。
	   如果未实现,driver和bus_info字段将根据netdev的父设备填写。 */
	void(*get_drvinfo)(struct net_device *, struct ethtool_drvinfo *);
	// 获取所需的缓冲区长度
	int	(*get_regs_len)(struct net_device *);
	// 获取设备寄存器
	void(*get_regs)(struct net_device *, struct ethtool_regs *, void *);
	// 获取是否启用了局域网唤醒
	void(*get_wol)(struct net_device *, struct ethtool_wolinfo *);
	// 打开或关闭局域网唤醒,返回负错误代码或零。
	int	(*set_wol)(struct net_device *, struct ethtool_wolinfo *);
	// 报告驱动程序消息级别。 这应该是netif日志记录函数使用的msg_enable字段的值。
	u32	(*get_msglevel)(struct net_device *);
	// 设置驱动程序消息级别
	void(*set_msglevel)(struct net_device *, u32);
	// 重新启动自动协商。 返回负错误代码或零。
	int	(*nway_reset)(struct net_device *);
	// 报告物理链路是否已启动。仅当网络开发启动时才会调用。通常应设置为 ethtool_op_get_link(),它使用 netif_carrier_ok()。
	u32	(*get_link)(struct net_device *);
	// 从设备 EEPROM 读取数据。应填写 magic 字段,不需要检查长度是否为零或环绕。用从 offset 到 offset + len 的 eeprom 值填写数据参数。 将 len 更新为读取量。返回错误或零。
	int	(*get_eeprom_len)(struct net_device *);
	int	(*get_eeprom)(struct net_device *, struct ethtool_eeprom *, u8 *);
	// 将数据写入设备 EEPROM。应该验证 magic 字段。 不需要检查长度是否为零或环绕。 将 len 更新到写入的参数。 返回错误或零。
	int	(*set_eeprom)(struct net_device *, struct ethtool_eeprom *, u8 *);
	// 获取中断合并参数。 返回负错误代码或零。
	int	(*get_coalesce)(struct net_device *, struct ethtool_coalesce *);
	// 设置中断合并参数。 返回负错误代码或零。
	int	(*set_coalesce)(struct net_device *, struct ethtool_coalesce *);
	// 获取回环内存参数
	void(*get_ringparam)(struct net_device *, struct ethtool_ringparam *);
	// 设置回环设备参数
	int	(*set_ringparam)(struct net_device *, struct ethtool_ringparam *);
	// 获取暂停参数
	void(*get_pauseparam)(struct net_device *, struct ethtool_pauseparam*);
	// 设置暂停参数
	int	(*set_pauseparam)(struct net_device *, struct ethtool_pauseparam*);
	// 运行指定的自检	
	void(*self_test)(struct net_device *, struct ethtool_test *, u64 *);
	// 返回一组描述所请求对象的字符串
	void(*get_strings)(struct net_device *, u32 stringset, u8 *);
	/* 识别物理设备,例如通过闪烁连接到它的 LED。 
	   实现可以异步或同步更新指标,但在这两种情况下,它都必须快速返回。 
	   它最初使用参数 %ETHTOOL_ID_ACTIVE 调用,并且必须激活异步更新并返回零、返回负(错误)或
	   返回同步指示的正频率(例如,每秒一个开/关周期为 1)。 
	   如果它返回一个频率,那么它将以参数 %ETHTOOL_ID_ON 或 %ETHTOOL_ID_OFF 的间隔再次调用它,并应相应地设置指标的状态。 
	   最后,它被调用与参数%ETHTOOL_ID_INACTIVE 并且必须停用指示器。 返回负错误代码或零。*/
	int	(*set_phys_id)(struct net_device *, enum ethtool_phys_id_state);
	// 返回有关设备的扩展统计信息。仅当设备维护 &struct rtnl_link_stats64 中未包含的统计信息时,这才有用。
	void(*get_ethtool_stats)(struct net_device *, struct ethtool_stats *, u64 *);
	// 在任何其他操作之前调用的函数。 返回负错误代码或零。
	int	(*begin)(struct net_device *);
	// 在除 begin 之外的任何其他操作之后调用的函数。 即使其他操作失败,也会被调用。
	void(*complete)(struct net_device *);
	// 报告特定于驱动程序的功能标志。
	u32	(*get_priv_flags)(struct net_device *);
	// 设置特定于驱动程序的功能标志。 返回负错误代码或零。
	int	(*set_priv_flags)(struct net_device *, u32);
	// 获取 get_strings 将写入的字符串数。
	int	(*get_sset_count)(struct net_device *, int);
	// 获取 RX 流分类规则。 返回负(错误)代码或零。
	int	(*get_rxnfc)(struct net_device *, struct ethtool_rxnfc *, u32 *rule_locs);
	// 设置 RX 流分类规则。 返回负错误代码或零。
	int	(*set_rxnfc)(struct net_device *, struct ethtool_rxnfc *);
	// 将固件映像写入设备的闪存。返回负错误代码或零。
	int	(*flash_device)(struct net_device *, struct ethtool_flash *);
	// 重置设备(部分),由 enum ethtool_reset_flags 中的标志位掩码指定。 返回负错误代码或零。
	int	(*reset)(struct net_device *, u32 *);
	// 获取 RX 流哈希键的大小。如果此特定设备不支持,则返回零。
	u32	(*get_rxfh_key_size)(struct net_device *);
	// 获取 RX 流哈希间接寻址表的大小。如果此特定设备不支持,则返回零。
	u32	(*get_rxfh_indir_size)(struct net_device *);
	// 获取 RX 流哈希间接寻址表、哈希键和/或哈希函数的内容。返回负错误代码或零。
	int	(*get_rxfh)(struct net_device *, u32 *indir, u8 *key, u8 *hfunc);
	/* 设置 RX 流哈希间接寻址表、哈希键和/或哈希函数的内容。 
	   设置为 %NULL 或零的参数将保持不变。返回负错误代码或零。
 	   如果请求至少一个不受支持的更改,则必须返回错误代码。*/
	int	(*set_rxfh)(struct net_device *, const u32 *indir, const u8 *key, const u8 hfunc);
	int	(*get_rxfh_context)(struct net_device *, u32 *indir, u8 *key, u8 *hfunc, u32 rss_context);
	int	(*set_rxfh_context)(struct net_device *, const u32 *indir, const u8 *key, const u8 hfunc, u32 *rss_context, bool delete);
	// 获取通道数。
	void(*get_channels)(struct net_device *, struct ethtool_channels *);
	// 设置通道数。 返回负错误代码或零。
	int	(*set_channels)(struct net_device *, struct ethtool_channels *);
	// 获取指示当前转储长度、版本和设备的标志的转储标志。
	int	(*get_dump_flag)(struct net_device *, struct ethtool_dump *);
	// 获取转储数据。
	int	(*get_dump_data)(struct net_device *, struct ethtool_dump *, void *);
	// 为设备设置转储特定标志。
	int	(*set_dump)(struct net_device *, struct ethtool_dump *);
	// 获取时间戳和 PTP 硬件时钟功能。支持软件中传输时间戳的驱动程序应将其设置为 ethtool_op_get_ts_info()。
	int	(*get_ts_info)(struct net_device *, struct ethtool_ts_info *);
	// 获取插件模块中包含的 eeprom 的大小和类型。
	int (*get_module_info)(struct net_device *, struct ethtool_modinfo *);
	// 从插件模块获取 EEPROM 信息
	int (*get_module_eeprom)(struct net_device *, struct ethtool_eeprom *, u8 *);
	// 获取节能 (EEE) 支持和状态。
	int	(*get_eee)(struct net_device *, struct ethtool_eee *);
	// 设置 EEE 状态(启用/禁用)以及 LPI 计时器。
	int	(*set_eee)(struct net_device *, struct ethtool_eee *);
	int	(*set_tunable)(struct net_device *, const struct ethtool_tunable *, const void *);
	int	(*get_per_queue_coalesce)(struct net_device *, u32, struct ethtool_coalesce *);
	/* 获取每个队列的中断合并参数。它必须检查给定的队列编号是否有效。
	   如果 RX 和 TX 队列都没有此编号,则返回 -EINVAL。
	   如果只有 RX 队列或 TX 队列具有此数字,请将适用的字段设置为 ~0 并返回 0。返回负错误代码或零。 */
	int	(*get_tunable)(struct net_device *, const struct ethtool_tunable *, void *);
	/* 为每个队列设置中断合并参数。它必须检查给定的队列编号是否有效。
	   如果 RX 和 TX 队列都没有此编号,则返回 -EINVAL。
	   如果只有 RX 队列或 TX 队列具有此编号,请忽略不适用的字段。返回负错误代码或零。 */
	int	(*set_per_queue_coalesce)(struct net_device *, u32, struct ethtool_coalesce *);
	/* 定义后,优先于 %get_settings 方法。获取各种设备设置,包括以太网链路设置。
	   应忽略 %cmd 和 %link_mode_masks_nwords 字段(使用%__ETHTOOL_LINK_MODE_MASK_NBITS而不是后者),
	   对它们的任何更改都将被内核覆盖。返回负错误代码或零。 */
	int	(*get_link_ksettings)(struct net_device *, struct ethtool_link_ksettings *);
	/* 定义后,优先于 %set_settings 方法。设置各种设备设置,包括以太网链路设置。
	   %cmd 和 %link_mode_masks_nwords 字段应忽略(使用 %__ETHTOOL_LINK_MODE_MASK_NBITS 而不是后者),
	   对它们的任何更改都将被内核覆盖。返回负错误代码或零。 */
	int	(*set_link_ksettings)(struct net_device *, const struct ethtool_link_ksettings *);
	// 获取网络设备转发纠错参数。
	int	(*get_fecparam)(struct net_device *, struct ethtool_fecparam *);
	// 设置网络设备转发纠错参数。
	int	(*set_fecparam)(struct net_device *, struct ethtool_fecparam *);
	/* 返回有关 PHY 设备的扩展统计信息。仅当设备维护 PHY 统计信息并且无法使用标准 PHY 库帮助程序时,这才有用。
	   所有操作都是可选的(即函数指针可以设置为 %NULL),调用方必须考虑到这一点。 呼叫者必须持有 RTNL 锁。
	   有关更多文档,请参阅这些操作使用的结构。
	   请注意,对于使用以零长度数组结尾的结构的所有操作,数组在内核中单独分配,并作为附加参数传递给驱动程序。
	   有关通用 netdev 功能接口的文档,请参阅 &struct net_device 和 &struct net_device_ops。 */
	void(*get_ethtool_phy_stats)(struct net_device *, struct ethtool_stats *, u64 *);
};

ethtool_ops 初始化调用流程

func: ngbe_init_module
func: ngbe_assign_netdev_ops
func: ngbe_set_ethtool_ops
struct: ngbe_ethtool_ops
  • drivers/net/ethernet/netswift/ngbe/ngbe_ethtool.c 使用 ethtool_ops 赋值结构体
static struct ethtool_ops ngbe_ethtool_ops = {
	.get_link_ksettings = ngbe_get_link_ksettings,
	.set_link_ksettings = ngbe_set_link_ksettings,
	.get_drvinfo            = ngbe_get_drvinfo,
	.get_regs_len           = ngbe_get_regs_len,
	.get_regs               = ngbe_get_regs,
	.get_wol                = ngbe_get_wol,
	.set_wol                = ngbe_set_wol,
	.nway_reset             = ngbe_nway_reset,
	.get_link               = ethtool_op_get_link,
	.get_eeprom_len         = ngbe_get_eeprom_len,
	.get_eeprom             = ngbe_get_eeprom,
	.set_eeprom             = ngbe_set_eeprom,
	.get_ringparam          = ngbe_get_ringparam,
	.set_ringparam          = ngbe_set_ringparam,
	.get_pauseparam         = ngbe_get_pauseparam,
	.set_pauseparam         = ngbe_set_pauseparam,
	.get_msglevel           = ngbe_get_msglevel,
	.set_msglevel           = ngbe_set_msglevel,
	.self_test              = ngbe_diag_test,
	.get_strings            = ngbe_get_strings,
	.set_phys_id            = ngbe_set_phys_id,
	.get_sset_count         = ngbe_get_sset_count,
	.get_ethtool_stats      = ngbe_get_ethtool_stats,
	.get_coalesce           = ngbe_get_coalesce,
	.set_coalesce           = ngbe_set_coalesce,
	.get_rxnfc              = ngbe_get_rxnfc,
	.set_rxnfc              = ngbe_set_rxnfc,
	.get_eee                = ngbe_get_eee,
	.set_eee                = ngbe_set_eee,
	.get_channels           = ngbe_get_channels,
	.set_channels           = ngbe_set_channels,
	.get_ts_info            = ngbe_get_ts_info,
	.get_rxfh_indir_size    = ngbe_rss_indir_size,
	.get_rxfh_key_size      = ngbe_get_rxfh_key_size,
	.get_rxfh               = ngbe_get_rxfh,
	.set_rxfh               = ngbe_set_rxfh,
	.flash_device           = ngbe_set_flash,
};

void ngbe_set_ethtool_ops(struct net_device *netdev)
{
	netdev->ethtool_ops = &ngbe_ethtool_ops;
}
  • drivers/net/ethernet/netswift/ngbe/ngbe_main.c
void ngbe_assign_netdev_ops(struct net_device *dev)
{
	dev->netdev_ops = &ngbe_netdev_ops;
	ngbe_set_ethtool_ops(dev);
	dev->watchdog_timeo = 5 * HZ;
}
  • drivers/net/ethernet/netswift/ngbe/ngbe_main.c ngbe_probe() 调用 ngbe_assign_netdev_ops()
static struct pci_driver ngbe_driver = {
	.name     = ngbe_driver_name,
	.id_table = ngbe_pci_tbl,
	.probe    = ngbe_probe,
	.remove   = ngbe_remove,
#ifdef CONFIG_PM
	.suspend  = ngbe_suspend,
	.resume   = ngbe_resume,
#endif
	.shutdown = ngbe_shutdown,
	.sriov_configure = ngbe_pci_sriov_configure,
	.err_handler = &ngbe_err_handler
};

static int ngbe_probe(struct pci_dev *pdev,
				const struct pci_device_id __always_unused *ent)
{
	...
	/* autoneg default on */
	hw->mac.autoneg = true;

	/* assign netdev ops and ethtool ops */
	ngbe_assign_netdev_ops(netdev);

	strncpy(netdev->name, pci_name(pdev), sizeof(netdev->name) - 1);
	...
}

static int __init ngbe_init_module(void)
{
	...
#ifdef CONFIG_NGBE_DEBUG_FS
	ngbe_dbg_init();
#endif

	ret = pci_register_driver(&ngbe_driver);
	return ret;
	...
}
module_init(ngbe_init_module);

应用侧

ethtool.c

static const struct option {
	const char *opts;
	int want_device;
	int (*func)(struct cmd_context *);
	char *help;
	char *opthelp;
} args[] = {
	...
	{ "-t|--test", 1, do_test, "Execute adapter self test",
	  "               [ online | offline | external_lb ]\n" },
	...
}

该部分通过工具 ethtool -t 传入参数回掉 do_test 函数,其中 cmd_context 参数在 main 函数中解析生成

/* Context for sub-commands */
struct cmd_context {
	const char *devname;	/* net device name */ /*设备口名称*/
	int fd;			/* socket suitable for ethtool ioctl */ /*内核态进程与用户态交互接口采用socket*/
	struct ifreq ifr;	/* ifreq suitable for ethtool ioctl */ /*网卡接口名称*/
	int argc;		/* number of arguments to the sub-command */ /*参数个数*/
	char **argp;		/* arguments to the sub-command */ /*参数列表*/
};
static int do_test(struct cmd_context *ctx)
{
	enum {
		ONLINE = 0,	 //在线测试
		OFFLINE,	 //离线测试
		EXTERNAL_LB, //
	} test_type;
	int err;
	struct ethtool_test *test;
	struct ethtool_gstrings *strings;

	if (ctx->argc > 1)
		exit_bad_args();
	if (ctx->argc == 1) {
		if (!strcmp(ctx->argp[0], "online"))
			test_type = ONLINE;
		else if (!strcmp(*ctx->argp, "offline"))
			test_type = OFFLINE;
		else if (!strcmp(*ctx->argp, "external_lb"))
			test_type = EXTERNAL_LB;
		else
			exit_bad_args();
	} else {
		test_type = OFFLINE;
	}

	strings = get_stringset(ctx, ETH_SS_TEST,
				offsetof(struct ethtool_drvinfo, testinfo_len),
				1);
	if (!strings) {
		perror("Cannot get strings");
		return 74;
	}

	test = calloc(1, sizeof(*test) + strings->len * sizeof(u64));
	if (!test) {
		perror("Cannot allocate memory for test info");
		free(strings);
		return 73;
	}
	memset(test->data, 0, strings->len * sizeof(u64));
	test->cmd = ETHTOOL_TEST;
	test->len = strings->len;
	if (test_type == EXTERNAL_LB)
		test->flags = (ETH_TEST_FL_OFFLINE | ETH_TEST_FL_EXTERNAL_LB);
	else if (test_type == OFFLINE)
		test->flags = ETH_TEST_FL_OFFLINE;
	else
		test->flags = 0;
	err = send_ioctl(ctx, test);
	if (err < 0) {
		perror("Cannot test");
		free(test);
		free(strings);
		return 74;
	}

	err = dump_test(test, strings);
	free(test);
	free(strings);

	return err;
}

ethtool 工具调用如下

  • ifreq 结构体填充
  • 通过 ioctl 进行 ifreq 请求

ethtool ioctl 调用

#ifndef TEST_ETHTOOL
int send_ioctl(struct cmd_context *ctx, void *cmd)
{
	ctx->ifr.ifr_data = cmd;
	return ioctl(ctx->fd, SIOCETHTOOL, &ctx->ifr); //通信接口fd,
}
#endif
  • 参数一,通过socket创建套接字(这里有个知识点 《内核态和用户态通信-netlink》,可通过套接字进行通信)
  • 参数二,宏定义接口类型 /usr/include/linux/sockios.h #define SIOCETHTOOL 0x8946 /* Ethtool interface */
  • 参数三,任意类型扩展参数,当前传递 ifreq

完整调用过程:

ifreq请求
ethtool_ops回调
ethtool: do_test
linux: ioctl
ngbe: ngbe_diag_test

关于ioctl如何调用驱动,可查看如下文章,相当详细:《一文搞懂内核块设备操作之ioctl系统调用过程》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值