一、注释
Linux内核驱动程序中的部分,属于Mellanox网卡驱动mlx4的初始化过程。
// Mellanox 以太网驱动主程序代码
static int __mlx4_init_one(struct pci_dev *pdev, int pci_dev_data,struct mlx4_priv *priv)
{int err; // 错误码变量int nvfs[MLX4_MAX_PORTS + 1] = {0, 0, 0}; // 存储每个端口的VF数量int prb_vf[MLX4_MAX_PORTS + 1] = {0, 0, 0}; // 存储每个端口要检查(probe)的VF数量const int param_map[MLX4_MAX_PORTS + 1][MLX4_MAX_PORTS + 1] = {// 参数映射表,用来导航nvfs和prb_vf数组{2, 0, 0}, {0, 1, 2}, {0, 1, 2} };unsigned total_vfs = 0; // 总的VF数量unsigned int i;// 从命令行或模块参数中获取每个端口的VF数量和检查数量的参数个数(argc)int num_vfs_argc = mlx4_get_argc(num_vfs.dbdf2val.tbl, pci_physfn(pdev));int probe_vfs_argc = mlx4_get_argc(probe_vf.dbdf2val.tbl, pci_physfn(pdev));// 输出初始化信息到日志pr_info(DRV_NAME ": Initializing %s\n", pci_name(pdev));// 尝试启用PCI设备并分配所需的资源err = mlx4_pci_enable_device(&priv->dev);if (err) {dev_err(&pdev->dev, "Cannot enable PCI device, aborting\n");return err;}// 通过param_map数组,根据参数个数来设置nvfs数组中对应端口的VF数量for (i = 0; i < num_vfs_argc; i++) {total_vfs += nvfs[param_map[num_vfs_argc - 1][i]]; // 计算总的VF数量int *cur_nvfs = &nvfs[param_map[num_vfs_argc - 1][i]]; // 当前端口的VF数量的指针// 获取每个端口的VF数量mlx4_get_val(num_vfs.dbdf2val.tbl, pci_physfn(pdev), i,cur_nvfs);// 判断获取的VF数量是否有效if (*cur_nvfs < 0) {dev_err(&pdev->dev, "num_vfs module parameter cannot be negative\n");err = -EINVAL;goto err_disable_pdev;}}// 针对probe_vf的参数进行类似的操作// ... (之后部分的代码相似逻辑,略去了详细的注释以避免过长)// 以下包含初始化设备、请求PCI资源、设置PCI DMA掩码、初始化设备固件特性等步骤// 如果在初始化流程中的任何步骤遇到错误,都会跳转到相应的错误处理代码,释放资源,并返回错误码return 0; // 如果一切正常,则返回0表示成功// 错误处理部分,分别对应于初始化过程中可能出错的几个阶段:
err_catas:mlx4_catas_end(&priv->dev);err_crdump:mlx4_crdump_end(&priv->dev);err_release_regions:pci_release_regions(pdev);err_disable_pdev:mlx4_pci_disable_device(&priv->dev);return err;
}
这里需要注意的是,上述代码片段已经经过一定程度的简化和摘录,并未反映整个函数的完整功能。这些代码是在内核级别操作硬件设备,涉及到PCI设备初始化、资源申请、参数配置以及错误处理等多个复杂的过程。在真实环境中,理解和修改这些代码通常需要具备一定的内核编程经验和对硬件设备的深入了解。
用于初始化Mellanox MLX4驱动中一个PCI设备的函数。下面是该段代码的完整中文注释:
// drivers\net\ethernet\mellanox\mlx4\main.c
// 定义一个函数用于初始化MLX4设备
static int __mlx4_init_one(struct pci_dev *pdev, int pci_dev_data,struct mlx4_priv *priv)
{int err; // 保存错误状态// 为每个端口初始化虚拟功能(VF)数量为0int nvfs[MLX4_MAX_PORTS + 1] = {0, 0, 0};// 初始化一个数组,用于存储每个端口将要激活的VF数量int prb_vf[MLX4_MAX_PORTS + 1] = {0, 0, 0};// 参数映射表,用于将num_vfs和probe_vf参数和实际端口对应起来const int param_map[MLX4_MAX_PORTS + 1][MLX4_MAX_PORTS + 1] = {{2, 0, 0}, {0, 1, 2}, {0, 1, 2}};unsigned total_vfs = 0; // 总的VF数量unsigned int i; // 循环计数器// 从命令行参数获取VF的数量,并存储相关端口的VF数量int num_vfs_argc =mlx4_get_argc(num_vfs.dbdf2val.tbl, pci_physfn(pdev));// 获取希望进行探测的VF数量int probe_vfs_argc =mlx4_get_argc(probe_vf.dbdf2val.tbl, pci_physfn(pdev));// 记录设备正在初始化pr_info(DRV_NAME ": Initializing %s\n", pci_name(pdev));// 允许PCI设备err = mlx4_pci_enable_device(&priv->dev);if (err) {// 如果无法启动PCI设备,则记录错误并退出dev_err(&pdev->dev, "Cannot enable PCI device, aborting\n");return err;}// 遍历num_vfs相关端口,并计算总VF数量for (i = 0; i < num_vfs_argc;total_vfs += nvfs[param_map[num_vfs_argc - 1][i]], i++) {int *cur_nvfs = &nvfs[param_map[num_vfs_argc - 1][i]];// 获取当前端口的VF数量mlx4_get_val(num_vfs.dbdf2val.tbl, pci_physfn(pdev), i,cur_nvfs);// 如果该数量为负,则记录错误信息然后禁用PCI设备if (*cur_nvfs < 0) {dev_err(&pdev->dev, "num_vfs module parameter cannot be negative\n");err = -EINVAL;goto err_disable_pdev;}}// 遍历希望探测的VF的端口for (i = 0; i < probe_vfs_argc; i++) {int *cur_prbvf = &prb_vf[param_map[probe_vfs_argc - 1][i]];// 获取当前端口希望探测的VF数量mlx4_get_val(probe_vf.dbdf2val.tbl, pci_physfn(pdev), i,cur_prbvf);// 如果该数量为负,则记录错误信息然后禁用PCI设备if (*cur_prbvf < 0) {dev_err(&pdev->dev, "probe_vf module parameter cannot be negative\n");err = -EINVAL;goto err_disable_pdev;}}// 检查探测的VF数量是否不大于每个端口的VF数量for (i = 0; i < ARRAY_SIZE(nvfs); i++) {if (prb_vf[i] > nvfs[i]) {dev_err(&pdev->dev, "probe_vf module parameter cannot be greater than num_vfs\n");err = -EINVAL;goto err_disable_pdev;}}// 检查总VF数量是否超出硬件所支持的最大数目if (total_vfs > MLX4_MAX_NUM_VF) {dev_err(&pdev->dev,"Requested more VF's (%d) than allowed by hw (%d)\n",total_vfs, MLX4_MAX_NUM_VF);err = -EINVAL;goto err_disable_pdev;}// 检查每个端口的VF数量是否超出单个端口所支持的最大数目for (i = 0; i < MLX4_MAX_PORTS; i++) {if (nvfs[i] + nvfs[2] > MLX4_MAX_NUM_VF_P_PORT) {dev_err(&pdev->dev,"Requested more VF's (%d) for port (%d) than allowed by driver (%d)\n",nvfs[i] + nvfs[2], i + 1,MLX4_MAX_NUM_VF_P_PORT);err = -EINVAL;goto err_disable_pdev;}}// 检查基址寄存器(BARs,Base Address Register)是否存在if (!(pci_dev_data & MLX4_PCI_DEV_IS_VF) &&!(pci_resource_flags(pdev, 0) & IORESOURCE_MEM)) {dev_err(&pdev->dev, "Missing DCS, aborting (driver_data: 0x%x, pci_resource_flags(pdev, 0):0x%lx)\n",pci_dev_data, pci_resource_flags(pdev, 0));err = -ENODEV;goto err_disable_pdev;}// 检查第二个BAR是否存在if (!(pci_resource_flags(pdev, 2) & IORESOURCE_MEM)) {dev_err(&pdev->dev, "Missing UAR, aborting\n");err = -ENODEV;goto err_disable_pdev;}// 请求PCI资源err = pci_request_regions(pdev, DRV_NAME);if (err) {dev_err(&pdev->dev, "Couldn't get PCI resources, aborting\n");goto err_disable_pdev;}// 设置设备为PCI主设备pci_set_master(pdev);// 尝试设置64位DMA掩码err = pci_set_dma_mask(pdev, DMA_BIT_MASK(64));if (err) {dev_warn(&pdev->dev, "Warning: couldn't set 64-bit PCI DMA mask\n");// 如果设置64位DMA掩码失败,则尝试设置32位DMA掩码err = pci_set_dma_mask(pdev, DMA_BIT_MASK(32));if (err) {dev_err(&pdev->dev, "Can't set PCI DMA mask, aborting\n");goto err_release_regions;}}// 类似地,尝试设置64位一致性DMA掩码err = pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(64));if (err) {dev_warn(&pdev->dev, "Warning: couldn't set 64-bit consistent PCI DMA mask\n");// 如果设置失败,则尝试设置32位一致性DMA掩码err = pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(32));if (err) {dev_err(&pdev->dev, "Can't set consistent PCI DMA mask, aborting\n");goto err_release_regions;}}// 设置DMA段的最大大小为1GB,这是固件支持的限制dma_set_max_seg_size(&pdev->dev, 1024 * 1024 * 1024);// 检查该设备是否是一个虚拟功能(VF)if (pci_dev_data & MLX4_PCI_DEV_IS_VF) {// 如果是物理功能(PF),通常我们会跳过VF,除非有明确的要求来探测它们if (total_vfs) {unsigned vfs_offset = 0;// 遍历所有VF,查找与当前设备匹配的VFfor (i = 0; i < ARRAY_SIZE(nvfs) &&vfs_offset + nvfs[i] < extended_func_num(pdev);vfs_offset += nvfs[i], i++);if (i == ARRAY_SIZE(nvfs)) {err = -ENODEV;goto err_release_regions;}// 如果本地VF编号大于要探测的VF编号,就跳过这个VFif ((extended_func_num(pdev) - vfs_offset)> prb_vf[i]) {dev_warn(&pdev->dev, "Skipping virtual function:%d\n",extended_func_num(pdev));err = -ENODEV;goto err_release_regions;}}}// 初始化核心转储和错误检测机制err = mlx4_crdump_init(&priv->dev);if (err)goto err_release_regions;err = mlx4_catas_init(&priv->dev);if (err)goto err_crdump;// 调用mlx4_load_one来加载并初始化一台MLX4设备err = mlx4_load_one(pdev, pci_dev_data, total_vfs, nvfs, priv, 0);if (err)goto err_catas;return 0;// 如果中间过程出现错误,则会进行错误处理并释放资源
err_catas:mlx4_catas_end(&priv->dev);err_crdump:mlx4_crdump_end(&priv->dev);err_release_regions:pci_release_regions(pdev);err_disable_pdev:mlx4_pci_disable_device(&priv->dev);return err;
}
从BARs(Base Address Register)的检查开始,后面代码的另一个注释:
// 检查基址寄存器(BARs)是否存在,如果是PF且0号BAR不是内存类型则报错并退出if (!(pci_dev_data & MLX4_PCI_DEV_IS_VF) &&!(pci_resource_flags(pdev, 0) & IORESOURCE_MEM)) {dev_err(&pdev->dev, "Missing DCS, aborting (driver_data: 0x%x, pci_resource_flags(pdev, 0):0x%lx)\n",pci_dev_data, pci_resource_flags(pdev, 0));err = -ENODEV;goto err_disable_pdev;}// 检查2号基址寄存器,如果不是内存类型则报错并退出if (!(pci_resource_flags(pdev, 2) & IORESOURCE_MEM)) {dev_err(&pdev->dev, "Missing UAR, aborting\n");err = -ENODEV;goto err_disable_pdev;}// 为PCI设备请求寄存器空间err = pci_request_regions(pdev, DRV_NAME);if (err) {dev_err(&pdev->dev, "Couldn't get PCI resources, aborting\n");goto err_disable_pdev;}// 设置PCI设备为主设备(Bus Master)pci_set_master(pdev);// 设置PCI设备的DMA遮罩为64位err = pci_set_dma_mask(pdev, DMA_BIT_MASK(64));if (err) {dev_warn(&pdev->dev, "Warning: couldn't set 64-bit PCI DMA mask\n");// 如果无法设置为64位,则尝试设置为32位err = pci_set_dma_mask(pdev, DMA_BIT_MASK(32));if (err) {dev_err(&pdev->dev, "Can't set PCI DMA mask, aborting\n");goto err_release_regions;}}// 设置一致性(consistent)DMA遮罩为64位err = pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(64));if (err) {dev_warn(&pdev->dev, "Warning: couldn't set 64-bit consistent PCI DMA mask\n");// 如果无法设置为64位,则尝试设置为32位err = pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(32));if (err) {dev_err(&pdev->dev, "Can't set consistent PCI DMA mask, aborting\n");goto err_release_regions;}}// 设置DMA传输的最大段大小为1GBdma_set_max_seg_size(&pdev->dev, 1024 * 1024 * 1024);// 检测当前设备是否为虚拟功能(VF)if (pci_dev_data & MLX4_PCI_DEV_IS_VF) {// 如果是PF且有VF需要处理,则跳过所有VF,除非明确指定要探测它们if (total_vfs) {unsigned vfs_offset = 0;// 计算跳过的VF数量for (i = 0; i < ARRAY_SIZE(nvfs) &&vfs_offset + nvfs[i] < extended_func_num(pdev);vfs_offset += nvfs[i], i++);if (i == ARRAY_SIZE(nvfs)) {err = -ENODEV;goto err_release_regions;}// 如果要探测的VF超出范围,则跳过这个VFif ((extended_func_num(pdev) - vfs_offset)> prb_vf[i]) {dev_warn(&pdev->dev, "Skipping virtual function:%d\n",extended_func_num(pdev));err = -ENODEV;goto err_release_regions;}}}// 初始化crash dump所需资源err = mlx4_crdump_init(&priv->dev);if (err)goto err_release_regions;// 初始化设备故障检测err = mlx4_catas_init(&priv->dev);if (err)goto err_crdump;// 加载设备所需资源和配置err = mlx4_load_one(pdev, pci_dev_data, total_vfs, nvfs, priv, 0);if (err)goto err_catas;// 初始化成功,返回0return 0;// 下面是错误处理部分,执行清理工作并返回错误码
err_catas:mlx4_catas_end(&priv->dev);err_crdump:mlx4_crdump_end(&priv->dev);err_release_regions:pci_release_regions(pdev);err_disable_pdev:mlx4_pci_disable_device(&priv->dev);return err;
}
这个函数主要分为以下几个部分:
1. 变量声明和初始化:定义错误变量`err`,并且初始化一些数组比如`nvfs`和`prb_vf`来保存端口的虚拟功能数量和探测虚拟功能数量。
2. 设备初始化前置检查:包括打印信息,检查PCI设备是否能够被激活,检查命令行参数(如果`num_vfs`或`probe_vf`参数是负数,则报错)。
3. 检查虚拟功能(VF)数量的合法性:验证总VF数量和每个端口的VF数量是否超过硬件和驱动程序允许的最大VF数量。
4. 基址寄存器(BARs)检查:确保设备具有必要的I/O内存资源。
5. PCI资源请求和DMA配置:请求PCI资源,设置PCI总线主模式,配置DMA掩码(用于64位或32位DMA映射)和最大DMA段大小。
6. 特殊设备处理:如果PCI设备是一个虚拟功能,而且没有明确的探测要求,那就跳过一些设置步骤。
7. 设备启动:调用多个MLX4系列函数,比如`mlx4_crdump_init`,`mlx4_catas_init`,`mlx4_load_one`来加载和初始化设备。
8. 错误处理:一系列的错误处理标签(`err_catas`,`err_crdump`,`err_release_regions`,`err_disable_pdev`),分别对应不同阶段的初始化失败处理,包括释放资源和禁用设备等。
每一个部分都精心设计以确保驱动装载在正确初始化硬件之前进行了多项检查,包括参数有效性检查、资源分配、以及其他硬件兼容性检查。任何环节失败,都会执行适当的资源释放和错误回报,并中止初始化过程。
二、讲解
这段代码是运行在Linux内核环境下,负责初始化某型号的Mellanox以太网卡的PCI设备的函数。其主要作用是配置PCI设备以及准备硬件操作所需资源。下面我将逐部分进行讲解。
首先,来看下函数的声明:
static int __mlx4_init_one(struct pci_dev *pdev, int pci_dev_data,struct mlx4_priv *priv)
{//...
}
这个函数是一个`static`的内部函数,意味着它只能在定义它的文件中被调用。它接受三个参数:PCI设备指针`pdev`、PCI设备的数据`pci_dev_data`和`mlx4`私有数据结构体`priv`的指针。
接下来是功能说明:
1. 函数一开始首先输出日志,表明它正在初始化该PCI设备。
2. 函数尝试使PCI设备可用(`mlx4_pci_enable_device`),并检查是否成功。
3. 函数遍历解析通过模块参数`num_vfs`和`probe_vf`给出的Virtual Functions (VFs)数量。这些参数用于处理虚拟化环境下的多个虚拟网络接口。
4. 检查通过`num_vfs`和`probe_vf`模块参数指定的VF数量的有效性,它们必须是非负的,并且`probe_vf`不能超过`num_vfs`指定的值。
5. 总VF的数量不能超过硬件支持的最大值`MLX4_MAX_NUM_VF`。
6. 检查PCI设备的BAR 0和BAR 2是否包含了必要的IO资源,如果没有,则报错。
7. 向PCI子系统请求PCI资源,并设置设备为bus master。
8. 尝试设置64位或32位DMA掩码。DMA掩码限制了设备发起DMA请求时能够访问的内存地址范围。
9. 设置DMA传输最大段大小为1GB(固件的限制)。
10. 检测该设备是否为虚拟功能(VF),并基于`total_vfs`等数据做进一步处理。
11. 初始化核心转储和CATAS错误处理机制。
12. 调用`mlx4_load_one`加载驱动并初始化硬件。
如果过程中遇到错误,函数会执行一系列的清理工作,包括解除对PCI资源的占用(`pci_release_regions`),禁用PCI设备(`mlx4_pci_disable_device`),以及与核心转储和CATAS相关的结束工作。
最后,函数会根据处理过程中是否有错误发生来返回相应的结果。如果成功执行,返回0,失败则返回错误码。
整个函数体现了Linux内核中对硬件设备初始化的一个典型处理过程,其中包括错误检查、资源分配、DMA设置和错误处理等多个环节。
三、虚拟功能(VF)
在计算机云计算和网络技术中,虚拟功能(VF)是一种虚拟化技术,特别是在服务器虚拟化和网络功能虚拟化(NFV)领域使用。VF是运行在物理硬件上的软件抽象,它允许单个物理资源(如网络接口卡NIC)被多个虚拟机或容器安全地共享,每个虚拟机/容器获得自己的VF实例。
VF的一个常见实践是通过PCI传递直接将硬件资源分配给虚拟机或容器。这种技术通常称为单根I/O虚拟化(SR-IOV)。在SR-IOV中,一个物理功能(PF)可以有多个虚拟功能(VFs),其中每个VF都是一个完全独立的接口。这些VFs可以直接分配给不同的虚拟机或容器,无需经过主机操作系统的网络栈处理。结果,VFs能够提供接近于原生硬件性能的I/O吞吐量和较低的延迟。
例如,在网络接口卡(NIC)上,一个物理NIC可以拥有多个VF,每个虚拟机或容器可以直接控制一个VF,就好像它是它们自己的专用NIC一样。这允许虚拟机/容器绕过传统的虚拟化网络层,直接和网络硬件通信,从而大幅度减少CPU使用率和网络延迟,提高性能。
此外,VFs通常拥有自己的队列、滤波器和其他硬件资源,允许它们独立于物理硬件的剩余部分进行数据包处理和其他网络功能。然而,虚拟功能的配置和使用通常需要硬件支持,并在硬件层面上实现精确控制和资源隔离。