E810 基于 100G 以太网控制器核心模块,该模块用于多种英特尔产品。图 1-1 说明了控制器核心逻辑与组成完整 E810 设备的 I/O 和支持功能之间的关系
LAN 引擎在虚拟化和非虚拟化场景中为传统 LAN 流量实现主机编程接口。 E810 实现了 768 个 VSI(虚拟端口),用于将流量分配到 PCI 物理功能PF和虚拟功能VF。 这些VSI可以通过16K Tx-Queues和2K Tx-Queues连接到主机
PE-协议引擎实现 iWARP 和 RoCEv2 RDMA 功能。 协议引擎从主机卸载协议处理,将接收到的数据有效负载直接放入用户缓冲区,无需主机 CPU 参与,并通过将 RDMA 编程接口直接映射到应用程序地址空间来消除执行 I/O 时的用户内核上下文切换。 协议引擎实现了最新的 RDMA 功能,包括发送队列推送模式
传输调度程序 (Tx-Scheduler) 确定发送到网络的数据包的顺序和时间。 它从 LAN 和 RDMA 队列接收请求(“门铃”)并安排这些请求进行传输。 调度决策允许从主机内存中提取给定队列或队列组中给定的字节或数据包“量”并将其发送到网络,Tx 调度程序的主要功能是:
Tx 和 Rx 修改器分别对 Tx 和 Rx 管道中的数据包内容执行操作。 修改器对数据包标头执行更新,例如 L2 标签(例如 VLAN)的插入和删除,以及标头和有效负载校验和的计算(并在 Rx 中拆分)(在 Tx 上插入并在 Rx 上验证)。 Tx 修改器还执行 TCP 分段卸载 (TSO)
Probe流程:
intel, ice, eth, drivers/net/ethernet/intel/ice/ice_main.c
ice_probe(struct pci_dev *pdev, const struct pci_device_id __always_unused *ent)
struct ice_pf *pf
struct ice_hw *hw
is_kdump_kernel -> 当在 kdump 内核下时,在启用设备之前启动重置,以清除任何挂起的 DMA 事务。 这些事务可能会导致某些系统在执行下面的 pcim_enable_device() 时进行机器检查
pci_save_state
pci_clear_master
pcie_flr -> ice:在故障转储内核中首先重置 当系统在发生紧急情况后启动到故障转储内核时,ice 网络设备可能仍然有挂起的事务,这些事务可能会在设备重新启用时导致错误或机器检查。 这可以防止故障转储内核加载驱动程序或收集故障数据。 为了避免此问题,请在崩溃内核上启用之前,通过 PCIe 配置空间在 Ice 设备上执行功能级别重置 (FLR)。 这将清除所有未完成的事务并停止所有队列和中断。 恢复FLR后的配置空间,否则测试时发现驱动加载不成功。 以下序列导致原始问题: - 使用 modprobe ice 加载ice驱动程序 - 使用 2 个 VF 启用 SR-IOV:echo 2 > /sys/class/net/eth0/device/sriov_num_vfs - 使用 echo c > /proc 触发崩溃 /sysrq-trigger - 使用 modprobeice 再次加载 Ice 驱动程序(或让它自动加载) - 系统在 pcim_enable_device() 期间再次崩溃
pci_restore_state
pcim_enable_device -> devres 是一种资源管理机制, 类似于一种垃圾收集处理器. 而资源的处理时机在 driver 的 install / remove 时候. 这样我们在为 device 分配相关资源之后, 就不必要关心如何释放它们了. 与 device 相关的资源有 memory / dma / iomap / regmap / interrupt / gpio 等, 这些资源都可以用 devres 机制管理起来, 使用相关资源封闭的 devres 接口, 就可以让这些资源自动销毁, 设备资源管理: https://blog.csdn.net/tiantianhaoxinqing__/article/details/125959030
err = pcim_iomap_regions(pdev, BIT(ICE_BAR0), dev_driver_string(dev))
ice_allocate_pf -> 分配物理方法, 为该设备分配一个 devlink 实例,并将私有区域作为 PF 结构返回。 通过添加一个操作在展开时将其删除,可以通过 devres 来跟踪 devlink 内存
devlink_alloc(&ice_devlink_ops
devm_add_action_or_reset(dev, ice_devlink_free, devlink)
dma_set_mask_and_coherent
pci_set_master
...
pcim_iomap_table
pci_read_config_byte PCI_REVISION_ID
ice_set_ctrlq_len -> 设置硬件规格/参数
hw->adminq.num_rq_entries = ICE_AQ_LEN;
hw->adminq.num_sq_entries = ICE_AQ_LEN;
hw->adminq.rq_buf_size = ICE_AQ_MAX_BUF_LEN;
hw->adminq.sq_buf_size = ICE_AQ_MAX_BUF_LEN;
hw->mailboxq.num_rq_entries = PF_MBX_ARQLEN_ARQLEN_M;
hw->mailboxq.num_sq_entries = ICE_MBXSQ_LEN;
hw->mailboxq.rq_buf_size = ICE_MBXQ_MAX_BUF_LEN;
hw->mailboxq.sq_buf_size = ICE_MBXQ_MAX_BUF_LEN;
hw->sbq.num_rq_entries = ICE_SBQ_LEN;
hw->sbq.num_sq_entries = ICE_SBQ_LEN;
hw->sbq.rq_buf_size = ICE_SBQ_MAX_BUF_LEN;
hw->sbq.sq_buf_size = ICE_SBQ_MAX_BUF_LEN;
netif_msg_init -> static inline u32 netif_msg_init
err = ice_init(pf)
ice_init_dev
ice_init_hw
ice_set_mac_type
FIELD_GET(PF_FUNC_RID_FUNC_NUM_M, rd32(hw, PF_FUNC_RID)) -> 读寄存器
ice_reset
wr32(hw, GLGEN_RTRIG, val)
ice_check_reset
ice_get_itr_intrl_gran -> 带宽
ice_create_all_ctrlq
ice_init_all_ctrlq
ice_init_ctrlq(hw, ICE_CTL_Q_ADMIN)
ice_adminq_init_regs
ICE_CQ_INIT_REGS
ice_init_check_adminq
ice_aq_get_fw_ver
ice_fill_dflt_direct_cmd_desc ice_aqc_opc_get_ver
ice_aq_send_cmd
ice_sq_send_cmd_retry
ice_sq_send_cmd -> ATQ
ice_shutdown_ctrlq
ice_shutdown_sq
停止管理队列上的任务
ICE_FREE_CQ_BUFS
ice_free_cq_ring
dmam_free_coherent
ice_shutdown_rq
...
ice_init_ctrlq(hw, ICE_CTL_Q_SB)
ice_init_ctrlq(hw, ICE_CTL_Q_MAILBOX)
ice_fwlog_init -> Ice:配置固件日志记录,用户希望能够通过从 E8xx 设备检索固件日志来调试固件问题。 使用 debugfs 允许用户配置固件日志记录的日志级别和消息数量。 如果 E8xx 支持固件日志记录,则将在ice 驱动程序的 PCI 设备 ID 下创建文件“fwlog”。 如果该文件不存在,则 E8xx 不支持固件日志记录或系统上未启用 debugfs。 用户想要做的一件事是控制报告哪些事件。 用户可以读写“fwlog/modules/<模块名称>”来获取/设置日志级别。 固件中的每个模块都支持将 ht 记录为“fwlog/modules”下的文件,支持读取(查看当前日志级别是什么)和写入(更改日志级别)
ice_fwlog_set_supported
ice_aqc_opc_fw_logs_query
ice_fwlog_supported
ice_fwlog_get
ice_fwlog_alloc_ring_buffs
ice_debugfs_fwlog_init -> 创建debugfs所需的目录和文件, 关联相应的函数操作
debugfs_create_dir(name, ice_debugfs_root)
...
ice_clear_pf_cfg -> 清除任何现有 PF 配置(VSI、VSI 列表、交换机规则、端口配置、流量导向器过滤器等)
cmd ice_aqc_opc_clear_pf_cfg
wr32(hw, PFQF_FD_ENA, PFQF_FD_ENA_FD_ENA_M) -> 启用流过滤, Ice:初始化FlowDirector资源,FlowDirector允许基于ntuple规则进行重定向。 规则使用 ethtool set-ntuple 接口进行编程。 支持的操作包括重定向到队列和丢弃。 设置初始框架来处理 Flow Director 过滤器。 创建和分配资源来管理过滤器并将过滤器编程到硬件。 过滤器通过边带接口进行处理; 创建控制VSI来管理通过边带的通信和处理请求。 分配资源后,更新硬件表以接受完美的过滤器
ice_clear_pxe_mode
ice_init_nvm
无论 NVM 编程模式如何,都会存储 SR 大小,因为工厂生产线中可能会使用空白模式
ice_discover_flash_size -> ice:创建flash_info结构和单独的NVM版本,ice_nvm_info结构已经成为与flash版本相关的所有字段的垃圾场。 它包含 NVM 版本和 EETRACK id、OptionROM 信息结构、闪存大小、ShadowRAM 大小等。 未来的更改将添加从非活动 NVM 组读取 NVM 版本和 EETRACK ID 的功能。 为了使这更简单,将这些 NVM 版本信息字段提取到它们自己的结构中非常有用。 将ice_nvm_info重命名为ice_flash_info,并创建一个单独的ice_nvm_info结构,其中将包含eetrack和NVM映射版本。 将netlist_ver结构移动到ice_flash_info中,并重命名为ice_netlist_info以保持一致性。 修改静态ice_get_orom_ver_info以将option rom结构作为指针。 这使得硬件结构的哪一部分被修改更加明显。 对ice_get_netlist_ver_info 执行相同操作。 引入一个新的ice_get_nvm_ver_info函数,该函数将类似于ice_get_orom_ver_info和ice_get_netlist_ver_info,用于保持NVM版本提取代码共置
ice_acquire_nvm
ice_read_flat_nvm
ice_determine_active_flash_banks -> ice:缓存 NVM 模块库信息,ice flash 包含 NVM、Option ROM 和 Netlist 模块各两个副本。 每个存储体都有一个指针字和一个大小字。 为了正确地从活动闪存组中读取数据,驱动程序必须手动计算偏移量。 在 NVM 初始化期间,读取 Shadow RAM 控制字并确定每个 NVM 模块的哪个存储体处于活动状态。 此外,缓存大小和指针值以用于计算正确的偏移量
读取 Shadow RAM 控制字并确定 NVM、OROM 和网表模块的哪些存储体处于活动状态。 还读取并计算关联的指针和大小。 然后将这些值缓存到ice_flash_info结构中以供以后使用,以便计算从活动模块读取的正确偏移量
ice_read_sr_word
ice_get_nvm_ver_info -> ice:引入从闪存模块读取的功能,当从设备的闪存读取时,ice驱动程序有两个可用的接口。 首先,它可以通过允许指定模块 ID 的固件使用中介接口。 这允许从活动闪存组的特定模块读取。 第二个可用的接口是执行平面读取。 这允许对整个闪存的完全访问。 然而,使用它需要软件来处理计算模块位置并解释指针地址。 虽然大多数所需数据都可以通过方便的第一个接口访问,但某些闪存内容却不能。 这包括与选项 ROM 和 NVM 存储体相关的 CSS 标头信息,以及对用作执行闪存更新的暂存空间的“非活动”存储体的任何访问。 为了访问所有相关的闪存内容,软件必须使用平面读取。 不是强制所有流执行平面读取计算,而是引入一个用于从闪存读取的新抽象:ice_read_flash_module。 该函数提供了从请求模块的活动或非活动闪存库中读取数据的抽象。 该接口与通过固件提供的抽象非常相似,但允许访问附加模块,并提供请求访问两个闪存组的机制。 乍一看,这种抽象允许精确指定调用者希望读取的银行(第一或第二)可能是有意义的。 这实现起来更简单,但使用起来更困难。 实际上,大多数呼叫者只知道他们想要活跃的银行还是不活跃的银行。 不是强迫调用者自己确定从哪个存储体读取,而是根据“活动”与“非活动”来实现ice_read_flash_module。 这显着简化了调用者级别的实现,并且是对闪存内容的更有用的抽象。 利用这个新接口重构主要 NVM 版本信息的读取。 不使用固件中介的 ShadowRAM 函数,而是使用ice_read_flash_module 抽象。 为此,请注意 NVM 的大多数读取都将以 2 字节字块的形式进行。 为了简化这种情况下ice_read_flash_module的使用,引入了ice_read_nvm_module。 这是ice_read_flash_module的一个简单包装,它获取NVM存储体的正确指针地址,并将2字节字格式强制传递给调用者。 读取 NVM 版本时,某些字段是从 Shadow RAM 中读取的。 Shadow RAM 是闪存的第一个 64KB,在设备加载期间填充。 大多数字段是从活动 NVM 组内的部分复制的。 为了从活动和非活动 NVM 存储体中读取这些数据,我们需要的不是从闪存的前 64KB 读取,而是从正确的偏移量读取到 NVM 存储体中。 为此引入ice_read_nvm_sr_copy。 该函数包装了ice_read_nvm_module,并具有与ice_read_sr_word相同的接口,不同之处在于允许调用者指定是否读取活动或非活动闪存组。 通过此更改,现在可以轻松重构ice_get_nvm_ver_info以使用软件介导的ice_read_flash_module接口进行读取,而不是依赖于固件介导的接口。 这将在以下更改中使用,以实现对 devlink 信息报告中存储版本的支持。 此外,将使用和扩展整个ice_read_flash_module接口以支持所有三个主要闪存组,并另外支持读取闪存映像安全修订信息
ice_get_orom_ver_info -> ice:通过devlink信息显示存储的UNDI固件版本,就像我们最近添加了对其他存储的固件闪存版本的支持一样,支持通过devlink信息显示存储的UNDI选项ROM版本。 为此,我们需要引入一个新的ice_get_inactive_orom_ver函数。 这比其他 Flash 版本有点棘手。 选项 ROM 版本数据是从 NVM 保留字段区域的特殊“引导配置”块中读取的。 该块仅包含*活动*选项 ROM 版本数据。 当设备固件完成更新选项 ROM 时,它会被填充。 此方法在读取存储的选项 ROM 版本数据时无效。 不用从闪存的这一部分读取,而是用从选项 ROM 二进制文件中查找组合版本信息的版本提取替换此版本提取。 该数据以简单的结构化格式存储在选项 ROM 中,偏移量为 512 字节。 该结构使用简单的模 256 校验和进行完整性验证。 扫描选项 ROM 以找到 CIVD 数据部分,并提取 Combo 版本。 重构ice_get_orom_ver_info,使其采用bank select枚举参数。 使用它来实现ice_get_inactive_orom_ver。 尽管所有ice器件在NVM PFA中都有引导配置块,但并非所有器件都有有效的选项ROM。 在这种情况下,旧的ice_get_orom_ver_info将“成功”,但报告全零的版本。 新的实现将无法在选项 ROM 中找到 $CIV 部分并报告错误。 因此,我们必须确保如果ice_get_orom_ver_info失败,ice_init_nvm不会失败。 使用新的ice_get_inactive_orom_ver允许通过devlink信息报告待更新的选项ROM版本
ice_get_netlist_info
ice_read_netlist_module
ice_read_flash_module -> Ice:通过devlink info显示存储的网表版本,添加读取非活动网表库以获取版本信息的功能。 为了支持这一点,重构我们读取网表版本数据的方式。 不使用带有模块 ID 的固件 AQ 接口,而是使用ice_read_flash_module 从闪存中读取作为平面 NVM。 此更改需要对所使用的偏移值进行轻微调整,因为从平面 NVM 读取包括类型字段(之前已被固件剥离)。 清理宏名称并将它们移动到ice_type.h。 为了清楚地说明我们如何计算偏移量,并使程序员可以轻松地将偏移值映射到数据表,请使用包装宏来考虑偏移量调整。 使用新添加的ice_get_inactive_netlist_ver函数从待处理的网表模块更新中提取版本数据。 将存储的“fw.netlist”和“fw.netlist.build”变体添加到信息版本映射数组中。 通过此更改,我们现在将“fw.netlist”和“fw.netlist.build”版本报告到 devlink 信息报告的存储部分。 与主要 NVM 模块版本一样,如果没有挂起的更新,我们会报告存储的当前活动值
ice_get_flash_bank_offset
ice_acquire_nvm
ice_read_flat_nvm
ice_get_caps -> ice:获取交换机配置、调度程序配置和设备功能,此补丁通过获取交换机配置、调度程序配置和设备功能来添加初始化流程。 交换机配置:启动时,会在每个物理功能的固件中创建一个 L2 交换机元素。 每个物理功能还映射到其交换元件所连接的端口。 换句话说,该交换机可以被视为嵌入式 vSwitch,可以将物理功能的虚拟站接口 (VSI) 连接到出口/入口端口。 最终将创建出口/入口过滤器并将其应用到该开关元件上。 作为初始化流程的一部分,驱动程序从该开关元件获取配置数据并存储它。 调度程序配置:Tx 调度程序是负责设置和实施 QoS 的子系统。 作为初始化流程的一部分,驱动程序查询并存储给定物理功能的默认调度程序配置。 设备功能:作为初始化的一部分,驱动程序必须确定设备的功能(例如最大队列、VSI 等)。 该信息从固件获取并由驱动程序存储
ice_discover_dev_caps
ice_aq_list_caps
ice_parse_dev_caps
ice_parse_common_caps
ice_parse_valid_functions_cap
ice_parse_vsi_dev_caps
ice_parse_1588_dev_caps
ice_parse_fdir_dev_caps
ice_parse_sensor_reading_cap
...
ice_discover_func_caps
ice_aq_list_caps
ice_parse_func_caps
ice_get_initial_sw_cfg
ice_init_port_info
xa_init_flags(&hw->port_info->sched_node_ids, XA_FLAGS_ALLOC) -> ice:在ice_sched_node中引入新的参数,为了支持新的devlink-rate API,ice_sched_node结构需要存储一些额外的参数。 这包括 tx_max、tx_share、tx_weight 和 tx_priority。 将新字段添加到ice_sched_node结构中。 添加新功能以使用新参数配置硬件。 引入新的xarray来唯一标识节点
ice_sched_query_res_alloc
hw->layer_info = devm_kmemdup -> 复制内存
ice_sched_get_psm_clk_freq -> ice:使用 PSM 时钟频率来计算 RL 配置文件,核心时钟频率目前硬编码为 446 MHz,用于 RL 配置文件计算。 这会导致问题,因为并非所有设备都使用该时钟频率。 读取 GLGEN_CLKSTAT_SRC 寄存器以确定选择哪个 PSM 时钟频率。 这可确保速率限制器配置文件计算正确
ice_sched_init_port
ice_sched_add_root_node
ice_sched_add_node
ice_aq_get_phy_caps
ice_aq_get_link_info
ice_cfg_rl_burst_size
ice_aq_manage_mac_read
ice_aq_set_mac_cfg
ice_alloc_fd_res_cntr
ice_init_hw_tbls -> ice:初始化DDP包结构,添加函数来初始化、解析和清理表示DDP包的结构。 包下载完成后,读取DDP包内容并将其存储到这些结构中。 此配置用于识别默认行为,稍后用于更新 HW 表条目
for (i = 0; i < ICE_BLK_COUNT; i++)
ice_init_flow_profs(hw, i)
...
if ice_is_pf_c827
ice_wait_for_fw
ice_init_feature_support -> Ice:添加功能位图、帮助程序和 DSCP 检查,DSCP 又名 L3 QoS 仅在某些设备上受支持。 为了强制执行此操作,此补丁引入了功能位图和辅助函数。 功能位图是根据驱动程序初始化时的设备 ID 设置的。 目前,DSCP 是该位图中的唯一功能,但将来会有更多功能。 在 DCB netlink 流程中,在执行 DSCP 之前检查功能位是否已设置
ice_set_feature_support
ice_is_phy_rclk_in_netlist
ice_is_cgu_in_netlist
ice_gnss_is_gps_present -> Ice:添加管理命令以访问cgu配置,添加固件管理命令以访问时钟生成单元配置,需要在驱动程序中启用扩展PTP和SyncE功能。 添加与时钟生成单元和访问数据的功能相关的输入和输出引脚的可能硬件变化的定义
ice:为E810T设备的GNSS模块添加TTY,添加新的ice_gnss.c文件用于保存基本的GNSS模块功能。 如果设备支持 GNSS 模块,请在适当的情况下调用新的ice_gnss_init 和ice_gnss_release 函数。 实现使用 TTY 设备从 GNSS 模块读取数据的基本功能。 添加I2C读取AQ命令。 现在需要通过 E810-T 适配器上的外部 I2C 端口扩展器来控制外部物理连接器。 未来的变化将引入写入功能
ice_request_fw
ice_init_pf
timer_setup(&pf->serv_tmr, ice_service_timer, 0)
INIT_WORK(&pf->serv_task, ice_service_task)
ice_mbx_init_snapshot
err = ice_init_interrupt_scheme(pf) -> 确定适当的中断方案, @pf:要初始化的板级私有结构
vectors = ice_ena_msix_range(pf)
pf->num_rdma_msix = num_cpus + ICE_RDMA_NUM_AEQ_MSIX -> 4
v_actual = pci_alloc_irq_vectors(pf->pdev, ICE_MIN_MSIX, v_wanted, PCI_IRQ_MSIX) -> 分配中断向量
ice_init_irq_tracker(pf, max_vectors, vectors)
ice_req_irq_msix_misc -> ice_req_irq_msix_misc - 设置 misc 向量以处理非队列事件 @pf:板级私有结构 这将设置 MSIX 0 的处理程序,用于管理非队列中断,例如 AdminQ 和错误。在 MSI 或传统中断模式下不使用
irq = ice_alloc_irq(pf, false) -> 为中断跟踪器保留一个中断, 用于混合中断, -> ice_alloc_irq - 分配新的中断向量 @pf:主板私有结构 @dyn_only:强制动态分配中断 为给定所有者 ID 分配新的中断向量。返回带有中断详细信息的 struct msi_map 并适当跟踪分配的中断。此函数从 irq_tracker 保留新的 irq 条目。如果根据跟踪器信息,所有使用 ice_pci_alloc_irq_vectors 分配的中断都已被使用,并且支持动态分配的中断,则将使用 pci_msix_alloc_irq_at 分配新中断。一些调用者可能只支持动态分配的中断。这用 dyn_only 标志表示。如果失败,则返回带有负 .index 的映射。调用者应该检查返回的映射索引
err = ice_alloc_vsis(pf)
pf->num_alloc_vsi = pf->hw.func_caps.guar_num_vsi
pf->vsi = devm_kcalloc(dev, pf->num_alloc_vsi, sizeof(*pf->vsi)
pf->vsi_stats = devm_kcalloc(dev, pf->num_alloc_vsi, sizeof(*pf->vsi_stats), GFP_KERNEL)
ice_init_pf_sw
ice_init_wakeup
ice_init_link
ice_send_version
ice_verify_cacheline_size
ice_set_safe_mode_vlan_cfg or
pcie_print_link_status
mod_timer(&pf->serv_tmr, round_jiffies(jiffies + pf->serv_tmr_period))
ice_init_eth(struct ice_pf *pf)
ice_get_main_vsi -> ice:添加 ice_get_main_vsi 来获取PF/main VSI,目前我们在多个地方使用 ice_find_vsi_by_type 来获取PF(又名主)VSI。 根据定义,PF VSI 始终是 pf->vsi 数组中的第一个元素(即 pf->vsi[0])。 因此,添加并使用新的辅助函数ice_get_main_vsi,它只返回 pf->vsi[0]
INIT_LIST_HEAD(&vsi->ch_list)
ice_cfg_netdev(vsi)
netdev = alloc_etherdev_mqs(sizeof(*np), vsi->alloc_txq, vsi->alloc_rxq)
set_bit(ICE_VSI_NETDEV_ALLOCD, vsi->state); <- DECLARE_BITMAP(state, ICE_VSI_STATE_NBITS)
ice_set_netdev_features(netdev) -> Ice:启用DDP包下载,尝试请求可选的特定于设备的DDP包文件(名称中带有PCIe设备序列号的文件,以便可以在不同的设备上使用不同的DDP包文件)。 如果可选包文件存在,请将其下载到设备中。 如果没有,请下载默认包文件。 根据 DDP 包文件是否存在以及尝试将其下载到设备的返回代码记录相应的消息。 如果下载失败并且设备上还没有软件包文件,请进入“安全模式”,其中某些功能不受支持
bool is_dvm_ena = ice_is_dvm_ena(&pf->hw) -> ice:为 PF netdev 通告 802.1ad VLAN 过滤和卸载,为了使驱动程序支持 802.1ad VLAN 过滤和卸载,它需要通告这些 VLAN 功能并支持修改这些 VLAN 功能,因此对ice_set_netdev_features 进行必要的更改( )。 默认情况下,为单 VLAN 模式和双 VLAN 模式 (SVM/DVM) 启用 CTAG 插入/剥离和 CTAG 过滤。 另外,在 DVM 中,默认启用 STAG 过滤。 这是通过设置 netdev->features 中的功能位来完成的。 此外,在 DVM 中,支持切换 STAG 插入/剥离,但默认情况下不启用它们。 这是通过设置 netdev->hw_features 中的功能位来完成的。 由于 802.1ad VLAN 过滤和卸载仅在 DVM 中受支持,因此请确保默认情况下不启用它们,并且当设备处于 SVM 中时,它们无法在运行时启用。 添加 ndo_fix_features() 回调的实现。 这是必需的,因为硬件无法支持同时进行 VLAN 插入/剥离的多个 VLAN 以太网类型,并且必须同时启用或禁用所有支持的 VLAN 过滤。 启用 DVM 时,默认禁用内部 VLAN 剥离。 如果 VSI 支持在 DVM 中剥离内部 VLAN,则必须在运行时进行配置。 例如,如果在启用 DVM 的情况下在端口 VLAN 中配置 VF,则将允许卸载内部 VLAN -> ice_is_dvm_ena - 检查是否启用了双 VLAN 模式,@hw:指向 HW 结构的指针 设备在初始化时配置为单或双 VLAN 模式,并且在运行时不能动态更改。 基于此,驱动程序无需每次需要了解 VLAN 模式时都进行 AQ 调用。 相反,请使用缓存 VLAN 模式。 ice:支持将设备配置为双 VLAN 模式 为了支持将设备配置为双 VLAN 模式 (DVM),DDP 和 FW 必须支持 DVM。 如果两者都支持DVM,则下载包的PF需要更新默认配方、设置VLAN模式并更新boost TCAM条目。 要支持更新 DVM 中的默认配方,请添加对更新现有交换机配方的 lkup_idx 和掩码的支持。 这是通过首先使用所需的配方 ID 调用获取配方 AQ (0x0292) 来完成的。 然后,如果成功,则更新查找索引之一 (lkup_idx) 及其关联的掩码(如果掩码有效),否则将使用已经存在的掩码。 在下载 DDP 时(特别是在下载 DDP 之后)保持全局配置锁定时,必须配置设备的 VLAN 模式。 如果支持,设备将默认为 DVM
dflt_features
csumo_features
vlano_features
tso_features
netif_set_tso_max_size(netdev, ICE_MAX_TSO_SIZE) -> ice:在 IPv6 上添加对 BIG TCP 的支持,启用在ice驱动程序中使用通用 ipv6_hopopt_jumbo_remove 帮助程序在 IPv6 上发送 BIG TCP 数据包来剥离 HBH 标头。 测试:netperf -t TCP_RR -H 2001:db8:0:f101::1 -- -r80000,80000 -O MIN_LATENCY,P90_LATENCY,P99_LATENCY,TRANSACTION_RATE 在两种不同的设置上进行测试。 在这两种情况下,加载更改的驱动程序后都会应用以下设置: ip link set dev enp175s0f1np1 gso_max_size 130000 ip link set dev enp175s0f1np1 gro_max_size 130000 ip link set dev enp175s0f1np1 mtu 9000 首次设置:之前:最小第 90 个第 99 个事务延迟百分比百分率微秒 延迟 延迟 Tran/s 微秒 微秒 134 279 410 3961.584 之后:最小第 90 个第 99 个事务延迟百分位数 百分率微秒 延迟 延迟 Tran/s 微秒 微秒 135 178 216 6093.404 其他设置: 之前:最小第 90 个第 99 个事务延迟百分位数 百分率微秒延迟时间 Tran/s 微秒 微秒 218 414 478 2944.765 之后:最小第 90 个第 99 个事务延迟百分位数 百分率微秒延迟 延迟 Tran/s 微秒 微秒 146 238 266 4700.596 -> net:不允许用户空间解除设备限制,直到提交 46e6b992c250(“rtnetlink:允许在设备创建时设置 GSO 最大值”)设备的 gso_max_segs 和 gso_max_size 不受用户空间控制。 由于以下设置,引用的提交添加了控制它们的能力:netns A | netns B veth<->veth eth0 如果 eth0 有 TSO 限制,并且用户希望在 eth0 和 veth 之间有效转发流量,他们应该将 eth0 的 TSO 限制复制到 veth 上。 对于 macvlans 或 ipvlan 来说,这会自动发生,但 veth 用户就没那么幸运了(考虑到松散耦合)。 不幸的是,有问题的提交还允许用户覆盖真实硬件设备的限制。 控制最大 GSO 大小可能很有用,并且有人可能正在使用该功能(据我所知,没有任何用户),因此创建一组单独的旋钮来可靠地记录 TSO 限制。 验证用户请求
ice_set_ops -> ice:使用xdp multi-buff更新xdp_features,现在ice驱动程序支持xdp multi-buffer,因此将其添加到xdp_features。 设置 xdp_features 标志之前检查 vsi 类型
if (ice_is_safe_mode(pf))
netdev->netdev_ops = &ice_netdev_safe_mode_ops
netdev->ethtool_ops = &ice_ethtool_safe_mode_ops
netdev->netdev_ops = &ice_netdev_ops
netdev->udp_tunnel_nic_info = &pf->hw.udp_tunnel_nic -> ice:转换为新的udp_tunnel基础设施,将ice转换为新的基础设施,使用共享端口表。 比平时多做一点错误检查,因为这个驱动程序确实有很多魔力。 我们需要计算固件保留的 VxLAN 和 GENEVE 条目的数量。 由于转换,驱动程序将不再休眠在原子部分
netdev->xdp_metadata_ops = &ice_xdp_md_ops -> ice:支持HW时间戳提示,使用之前重构的代码并创建一个允许XDP代码读取HW时间戳的函数。 另外,引入数据包上下文,其中将存储与提示相关的数据。 ice_xdp_buff 仅包含指向该结构的指针,以避免在本系列后面的 ZC 模式中复制它。 HW时间戳是驱动程序中第一个支持的提示,因此还要添加xdp_metadata_ops
ice_set_ethtool_ops(netdev)
netdev->ethtool_ops = &ice_ethtool_ops -> Ice:添加统计信息和ethtool支持,该补丁实现了看门狗任务以从设备获取数据包统计信息。 此补丁还添加了对以下 ethtool 操作的支持: ethtool devname ethtool -s devname [msglvl N] [msglevel type on|off] ethtool -g|--show-ring devname ethtool -G|--set-ring devname [rx N] [tx N] ethtool -i|--driver devname ethtool -d|--register-dump devname [raw on|off] [hex on|off] [文件名] ethtool -k|--show-features| --show-offload devname ethtool -K|--features|--offload devname 功能 on|off ethtool -P|--show-permaddr devname ethtool -S|--statistics devname ethtool -a|--show-pause devname ethtool -A|--pause devname [autoneg on|off] [rx on|off] [tx on|off] ethtool -r|--negotiate devname
ether_addr_copy(mac_addr, vsi->port_info->mac.perm_addr)
eth_hw_addr_set(netdev, mac_addr)
ice_vsi_cfg_netdev_tc(vsi, vsi->tc_cfg.ena_tc) -> Setup netdev TC information
netdev->max_mtu = ICE_MAX_MTU -> (9728 - (14 + 4 + (VLAN_HLEN * 2)))
ice_dcbnl_setup -> Ice:实现DCBNL支持,实现DCBNL子系统的接口层。 这些是支持 dcbnl_rtnl_ops 结构中定义的回调的函数。 这些回调将用于与设备的 DCB 设置进行交互。 dcb_nl集合函数的实现和支持SW DCB函数 -> Intel® Ethernet Data Center Bridging (DCB) Service (iSCSI) FAQ -> 适用于 iSCSI 的 ntel® 以太网数据中心桥接 (DCB) 服务与 Microsoft Server 2012* 本机 NIC 组合不兼容,适用于 iSCSI 的英特尔以太网 DCB 服务与 Microsoft Server 2012 NIC 组合(也称为负载平衡/故障转移 (LBFO))不兼容。 安装适用于 iSCSI 的英特尔以太网 DCB 服务时,请勿使用英特尔® 以太网 10 GB 端口创建 LBFO 组。 如果英特尔以太网 10 GB 端口是 LBFO 团队的一部分,请勿为 iSCSI 安装英特尔以太网 DCB 服务。 如果在同一端口上使用 iSCSI 和 LBFO 的英特尔以太网 DCB 服务,可能会出现安装失败和持续链路丢失的情况
netdev->dcbnl_ops = &dcbnl_ops
ice_dcbnl_set_all(vsi)
ice_init_mac_fltr
ice_devlink_create_pf_port
ice_devlink_set_port_split_options
ice_devlink_set_switch_id -> Ice:获取 switchdev 设备上的交换机 id,驱动程序上每个网络设备的交换机 id 应该相同。 同一系统上的设备之间的 id 必须唯一,但不同系统上的设备之间不需要唯一。 交换机 ID 用于定位交换机上的端口并了解聚合端口是否属于同一交换机。 为了满足此要求,请使用 pci_get_dsn 作为交换机 ID 值,因为这是同一系统上每个设备的唯一值。 kubernetes 的自动工具需要实现 switch id。 通过设置 devlink 端口属性并在创建 pf(用于上行链路)和 vf(用于表示器)devlink 端口时调用 devlink_port_attrs_set 来设置交换机 id。 获取交换机 ID(在 switchdev 模式下): cat /sys/class/net/$PF0/phys_switch_id
devlink_port_register_with_ops
devlink_port_register_with_ops(devlink, devlink_port, vsi->idx, &ice_devlink_port_ops)
ice_register_netdev
netif_carrier_off(vsi->netdev)
netif_tx_stop_all_queues(vsi->netdev) -> Ice:延迟netdev注册,一旦注册了netdev,相应的网络接口就可以立即被用户空间实用程序(例如NetworkManager)使用。 如果驱动程序在技术上尚未完全启动,这可能会出现问题。 将 netdev 注册移动到探测器的末尾,因为此时驱动程序数据结构和设备将按预期初始化。 但是,延迟 netdev 注册会导致 aRFS 流程失败,其中检查 netdev->reg_state == NETREG_REGISTERED 条件。 目前尚不清楚为什么要添加此检查,因此请将其删除。 本地测试并未表明此更改有任何问题。 ice_open 中的状态位检查是作为权宜之计,以防止过早的接口启动操作。 不再需要它,因此将其删除
for (i = 0; i < dev->num_tx_queues; i++)
netif_tx_stop_queue(txq)
ice_tc_indir_block_register -> ice:支持间接通知,实现间接通知机制以支持在隧道设备上卸载TC规则。 将间接阻止列表保留在 netdev priv 中。 通知会调用设置tc cls的花函数。 目前我们只能卸载入口类型。 其他流块活页夹不支持返回
flow_indr_dev_register(ice_indr_setup_tc_cb, np) -> net: flow_offload:整合间接 flow_block 基础设施,隧道设备不提供 dev->netdev_ops->ndo_setup_tc(...) 接口。 隧道设备和路由控制平面没有提供将隧道和物理设备关联起来的明显方式。 该补丁允许驱动程序通过 flow_indr_dev_register() 和 flow_indr_dev_unregister() 为 tc 和 netfilter 前端注册隧道设备卸载处理程序。 前端调用 flow_indr_dev_setup_offload() 来迭代提供隧道设备硬件卸载支持的驱动程序列表,并为此隧道设备设置流块。 如果删除驱动程序模块,则间接 flow_block 最终会出现陈旧的回调引用。 模块删除路径触发 dev_shutdown() 路径来删除物理设备的 qdisc 和 flow_blocks。 然而,这对于隧道设备来说没有用,因为物理设备和隧道设备之间的关系并不明确。 此补丁引入了一个清理回调,当删除驱动程序模块以清理隧道设备 flow_block 时会调用该回调。 该补丁定义了 struct flow_block_indr 并使用 flow_block_cb 中的它来存储前端在模块删除时执行 flow_block_cb 清理所需的信息
ice_napi_add
ice_for_each_q_vector(vsi, v_idx)
netif_napi_add(vsi->netdev, &vsi->q_vectors[v_idx]->napi, ice_napi_poll) -> NAPI polling Rx/Tx cleanup routine
ice_for_each_tx_ring(tx_ring, q_vector->tx)
wd = ice_xmit_zc(tx_ring)
or wd = ice_clean_tx_irq(tx_ring, budget)
...
ice_q_vector_set_napi_queues(vsi->q_vectors[v_idx], false)
err = ice_init_rdma(pf)
ice_is_rdma_ena(pf)
return test_bit(ICE_FLAG_RDMA_ENA, pf->flags) -> Check RDMA ENABLE/DISABLE
ret = ice_alloc_rdma_qvectors(pf) -> ice:添加单独的中断分配,目前中断分配,根据某个特性是批量分配的。 此外,分配后还有一系列操作,通过该批中断分配每个 irq 设置。 尽管驱动程序尚不支持动态中断分配,但将分配的中断保留在池中并添加分配抽象逻辑以使代码更加灵活。 将每个中断信息保留在 ice_q_vector 结构中,这会产生ice_vsi::base_vector冗余。 此外,因此有一些功能可以删除
pf->msix_entries = kcalloc(pf->num_rdma_msix, sizeof(*pf->msix_entries), GFP_KERNEL)
for (i = 0; i < pf->num_rdma_msix; i++)
struct msix_entry *entry = &pf->msix_entries[i]
struct msi_map map
map = ice_alloc_irq(pf, false) -> ice:添加动态中断分配,目前驱动程序只能在init阶段通过调用pci_alloc_irq_vectors分配中断向量。 对此进行更改并使用新的 pci_msix_alloc_irq_at/pci_msix_free_irq API,并在启用 MSIX 后启用分配和释放更多中断。 由于并非所有平台都支持动态分配,请使用 pci_msix_can_alloc_dyn 检查。 扩展跟踪器以跟踪最初分配的中断数量,因此当所有此类向量都已使用时,会自动动态分配其他中断。 记住每个中断分配方法,然后适当地释放。 由于某些功能可能需要动态分配的中断,因此添加适当的 VSI 标志并在分配新中断时将其考虑在内
为给定所有者 ID 分配新的中断向量。 返回包含中断详细信息的 struct msi_map 并适当跟踪分配的中断。 该函数从 irq_tracker 保留新的 irq 条目。 如果根据跟踪器信息,使用ice_pci_alloc_irq_vectors分配的所有中断都已使用并且支持动态分配的中断,则将使用pci_msix_alloc_irq_at分配新中断。 一些调用者可能只支持动态分配的中断。 这由 dyn_only 标志指示。 失败时,返回 .index 为负的映射。 调用者应该检查返回的map索引
struct ice_irq_entry *entry
entry = ice_get_irq_res(pf, dyn_only)
entry = kzalloc(sizeof(*entry), GFP_KERNEL)
ret = xa_alloc(&pf->irq_tracker.entries, &index, entry, limit, GFP_KERNEL)
entry->index = index
entry->dynamic = index >= num_static
pci_msix_can_alloc_dyn
pci_msix_alloc_irq_at or
map.index = entry->index
map.virq = pci_irq_vector(pf->pdev, map.index)
entry->entry = map.index;
entry->vector = map.virq
ice_plug_aux_dev -> 在每个 PCIe 设备功能的辅助总线上注册ice客户端辅助 RDMA 设备,以便辅助驱动程序 (irdma) 附加到。 它允许实现单个 RDMA 驱动程序 (irdma),该驱动程序能够通过支持 RDMA 的多代 Intel 硬件与多个 netdev 驱动程序配合使用。 ice 和 irdma 之间不存在加载顺序依赖性
auxiliary_device_init
auxiliary_device_add
ice_init_devlink
ice_init_features
ice_hwmon_init
hdev = hwmon_device_register_with_info(dev, "ice", pf, &ice_chip_info,
发包流程:
intel, eth, ice, send msg:
ice_start_xmit
tx_ring = vsi->tx_rings[skb->queue_mapping]
skb_put_padto(skb, ICE_MIN_TX_LEN) -> 填充缓冲区以确保尾随字节存在并被清空。 如果缓冲区已包含足够的数据,则不会更改。 否则延长。 成功时返回零。 skb 因错误而被释放。
ice_xmit_frame_ring(skb, tx_ring) -> Sends buffer on Tx ring
ice_trace(xmit_frame_ring, tx_ring, skb) -> Ice:添加跟踪点,此补丁仿照 Scott Peterson 为 i40e 开发的补丁。 通过新文件ice_trace.h 将跟踪点添加到驱动程序,并在驱动程序中有趣的位置添加一些新的跟踪调用。 添加一些 DIMLIB 跟踪以帮助调试中断调节问题。 性能不应受到影响,这对于将来调试和向路径添加新的跟踪事件非常有用。 注意 eBPF 程序可以附加到这些事件,并且 perf 可以对它们进行计数,因为我们附加到内核中的事件子系统
count = ice_xmit_desc_count(skb) -> calculate number of Tx descriptors needed
ice_txd_use_count
if (ice_chk_linearize(skb, count)) -> Check if there are more than 8 fragments per packet
__skb_linearize(skb)
ice_maybe_stop_tx -> ice:修复 PF 驱动程序中的 tx_timeout,在此提交之前,当队列压力足够大时,驱动程序会遇到 tx_timeouts。 发生这种情况是因为硬件尾部和软件尾部 (NTU) 错误地不同步。 因此,这导致硬件头部与硬件尾部发生冲突,这对于硬件来说意味着为 Tx 发布的所有描述符都已被处理。 由于驱动器中使用的 Tx 逻辑,SW 尾部和 HW 尾部允许不同步。 这样做是为了优化,因为它允许驱动程序尽可能不频繁地写入 HW 尾部,同时仍然更新 SW 尾部索引以进行跟踪。 然而,在某些情况下,这会导致尾部永远不会更新,从而导致 Tx 超时。 Tx HW tail 写入条件: if (netif_xmit_stopped(txring_txq(tx_ring) || !skb->xmit_more) writel(sw_tail, tx_ring->tail); 在 Tx 逻辑中发现问题,导致上述更新硬件的条件 tail 永远不会发生,导致 tx_timeouts。在ice_xmit_frame_ring中,我们根据内核交给我们的skb计算Tx事务需要多少个描述符,然后将其与一些额外的填充一起传递到ice_maybe_stop_tx,以确定我们是否有足够的可用描述符。 如果我们不这样做,那么我们将-EBUSY返回到堆栈,否则我们继续并最终在ice_tx_map中相应地准备Tx描述符并设置next_to_watch。在ice_tx_map中,我们再次调用ice_maybe_stop_tx,其值为MAX_SKB_FRAGS + 4。 这里的关键是这个值可能小于我们在ice_xmit_frame_ring中第一次调用ice_maybe_stop_tx时发送的值现在,如果未使用的描述符的数量在MAX_SKB_FRAGS + 4和ice_xmit_frame_ring中第一次调用ice_maybe_stop_tx时使用的值之间。 由于上面的“Tx HW tail 写入条件”,我们不更新 HW tail。 这是因为在ice_maybe_stop_tx中,我们从ice_maybe_stop_tx返回成功,而不是调用__ice_maybe_stop_tx并随后调用netif_stop_subqueue,这会设置__QUEUE_STATE_DEV_XOFF位。 然后,通过调用 netif_xmit_stopped 在“Tx HW tail 写入条件”中检查该位,如果设置了上述位,则随后更新 HW tail。 在ice_clean_tx_irq中,如果next_to_watch不为NULL,我们最终会清理HW设置DD位的描述符,并且我们有预算。 根据上段的描述,HW 头最终将遇到 HW 尾部。 下次通过ice_xmit_frame_ring,我们使用堆栈中的另一个skb对ice_maybe_stop_tx进行初始调用。 这次我们没有足够的可用描述符,我们将 NETDEV_TX_BUSY 返回到堆栈并最终将 next_to_watch 设置为 NULL。 这就是我们被困住的地方。 在ice_clean_tx_irq中,我们从不清理任何东西,因为next_to_watch始终为NULL,而在ice_xmit_frame_ring中,我们从不更新HW尾部,因为我们已经将NETDEV_TX_BUSY返回到堆栈,最终我们遇到了tx_timeout。 通过确保ice_tx_map 中对ice_maybe_stop_tx 的第二次调用传递的值大于等于ice_xmit_frame_ring 中对ice_maybe_stop_tx 的初始调用时使用的值,已修复此问题。 这是通过添加以下定义来使逻辑更加清晰并减少再次混乱的机会: ICE_CACHE_LINE_BYTES 64 ICE_DESCS_PER_CACHE_LINE (ICE_CACHE_LINE_BYTES / \ sizeof(structice_tx_desc)) ICE_DESCS_FOR_CTX_DESC 1 ICE_DESCS_FOR_SKB_DATA_PTR 1 ICE_CACHE_LINE _BYTES 为 64 是一个假设 这样我们就不必在每次通过 Tx 路径时都弄清楚这一点。 相反,我在ice_probe 中添加了健全性检查,以验证缓存行大小并在不是 64 字节时打印一条消息。 如果从 GLPCI_CNF2 寄存器读取时缓存行大小不是 64 字节,则可以更轻松地提交问题
netdev_txq_bql_enqueue_prefetchw -> prefetch bql data for write -> -ice:使用预取方法,内核提供了一些预取机制来加速接收处理期间的普通冷缓存行访问。 由于这些是软件结构,因此策略性地放置预取会有所帮助。 请注意,仅对非 XDP 队列调用 BQL 预取完成
prefetchw(&dev_queue->dql.num_queued)
first = &tx_ring->tx_buf[tx_ring->next_to_use]
ice_tx_prepare_vlan_flags(tx_ring, first)
tso = ice_tso(first, &offload)
csum = ice_tx_csum(first, &offload)
ice_tstamp(tx_ring, skb, first, &offload)
ice_tx_map(tx_ring, first, &offload) -> Build the Tx descriptor
dma_map_single -> 线性区的 skb->data 做 dma 映射,得到 硬件可以读取操作 dma 地址
tx_desc->cmd_type_offset_bsz = ice_build_ctob
size = skb_frag_size(frag)
skb_frag_dma_map
skb_tx_timestamp(first->skb)
wmb()
first->next_to_watch = tx_desc
ice_maybe_stop_tx(tx_ring, DESC_NEEDED)
notify HW of packet
kick = __netdev_tx_sent_queue(txring_txq(tx_ring), first->bytecount, netdev_xmit_more())
writel(i, tx_ring->tail)
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。