• 内核与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 *);
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    应用侧

    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" },
    	...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    该部分通过工具 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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 参数一,通过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系统调用过程》

  • 相关阅读:
    前端TypeScript学习-交叉类型与泛型
    antd4 icon使用svg
    测开 (Junit 单元测试框架)
    [VTK] vtkWindowedSincPolyDataFilter 源码注释解读
    如何做好商品的库存管理?哪些指标是衡量库存的指标
    pandas plot函数:数据可视化的快捷通道
    【算法速查】一篇文章带你快速入门八大排序(上)
    ArcGIS综合制图教程,简单上手!
    fastadmin列表页展示分类名称通用搜索按分类名称搜索
    低空经济是什么?
  • 原文地址:https://blog.csdn.net/qq_24423085/article/details/133345736