以下以网讯网卡 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 初始化调用流程
- 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
完整调用过程:
关于ioctl如何调用驱动,可查看如下文章,相当详细:《一文搞懂内核块设备操作之ioctl系统调用过程》