linux系统之五 网卡驱动初始化解析

发布时间 2023-11-05 16:11:38作者: 划水的猫

一、环境说明

内核版本:Linux 3.10

内核源码地址:https://elixir.bootlin.com/linux/v3.10/source (包含各个版本内核源码,且王页可全局搜索函数)

网卡:Intel的igb网卡

网卡驱动源码目录:drivers/net/ethernet/intel/igb/

二、网卡驱动的加载

网卡需要有驱动才能工作,驱动是加载到内核中的模块,负责衔接网卡和内核。
当相应的网卡收到数据包时,网络模块会调用相应的驱动程序处理数据。
网卡驱动程序 igb 向 Linux 内核通过 module_init 宏注册一个初始化函数 igb_init_module,当驱动加载的时候,该函数被内核调用。

// file: drivers/net/ethernet/intel/igb/igb_main.c
static struct pci_driver igb_driver = {
    .name     = igb_driver_name,
    .id_table = igb_pci_tbl, //所支持的设备列表
    .probe    = igb_probe, //探测函数
    .remove   = igb_remove,
    ......
};

static int __init igb_init_module(void)
{
    ......
    ret = pci_register_driver(&igb_driver);
    return ret;
}
module_init(igb_init_module);

igb_init_module()->pci_register_driver()->__pci_register_driver()->driver_register(),把网卡的驱动(driver)加载到内核 PCI 子系统。

三、网卡驱动的初始化

一个驱动程序可以支持一个或多个设备,而一个设备只会绑定一个驱动程序。
驱动程序将其支持的所有设备保存在一个列表 struct pci_device_id 中。
igb 驱动程序所支持的 PCI 设备列表部分如下:

// file: drivers/net/ethernet/intel/igb/igb_main.c
static DEFINE_PCI_DEVICE_TABLE(igb_pci_tbl) = {
    { PCI_VDEVICE(INTEL, E1000_DEV_ID_I354_BACKPLANE_1GBPS) },
    { PCI_VDEVICE(INTEL, E1000_DEV_ID_I354_SGMII) },
    { PCI_VDEVICE(INTEL, E1000_DEV_ID_I354_BACKPLANE_2_5GBPS) },
    { PCI_VDEVICE(INTEL, E1000_DEV_ID_I211_COPPER), board_82575 },
    { PCI_VDEVICE(INTEL, E1000_DEV_ID_I210_COPPER), board_82575 },
    { PCI_VDEVICE(INTEL, E1000_DEV_ID_I210_FIBER), board_82575 },
    { PCI_VDEVICE(INTEL, E1000_DEV_ID_I210_SERDES), board_82575 },
    { PCI_VDEVICE(INTEL, E1000_DEV_ID_I210_SGMII), board_82575 },
    { PCI_VDEVICE(INTEL, E1000_DEV_ID_I350_COPPER), board_82575 },
    { PCI_VDEVICE(INTEL, E1000_DEV_ID_I350_FIBER), board_82575 },
    { PCI_VDEVICE(INTEL, E1000_DEV_ID_I350_SERDES), board_82575 },
    { PCI_VDEVICE(INTEL, E1000_DEV_ID_I350_SGMII), board_82575 },
    { PCI_VDEVICE(INTEL, E1000_DEV_ID_82580_COPPER), board_82575 },
    { PCI_VDEVICE(INTEL, E1000_DEV_ID_82580_FIBER), board_82575 },
    { PCI_VDEVICE(INTEL, E1000_DEV_ID_82580_QUAD_FIBER), board_82575 },
    { PCI_VDEVICE(INTEL, E1000_DEV_ID_82580_SERDES), board_82575 },
    { PCI_VDEVICE(INTEL, E1000_DEV_ID_82580_SGMII), board_82575 },
    { PCI_VDEVICE(INTEL, E1000_DEV_ID_82580_COPPER_DUAL), board_82575 },
    { PCI_VDEVICE(INTEL, E1000_DEV_ID_DH89XXCC_SGMII), board_82575 },
    { PCI_VDEVICE(INTEL, E1000_DEV_ID_DH89XXCC_SERDES), board_82575 },
    { PCI_VDEVICE(INTEL, E1000_DEV_ID_DH89XXCC_BACKPLANE), board_82575 },
    { PCI_VDEVICE(INTEL, E1000_DEV_ID_DH89XXCC_SFP), board_82575 },
    { PCI_VDEVICE(INTEL, E1000_DEV_ID_82576), board_82575 },
    { PCI_VDEVICE(INTEL, E1000_DEV_ID_82576_NS), board_82575 },
    { PCI_VDEVICE(INTEL, E1000_DEV_ID_82576_NS_SERDES), board_82575 },
    { PCI_VDEVICE(INTEL, E1000_DEV_ID_82576_FIBER), board_82575 },
    { PCI_VDEVICE(INTEL, E1000_DEV_ID_82576_SERDES), board_82575 },
    { PCI_VDEVICE(INTEL, E1000_DEV_ID_82576_SERDES_QUAD), board_82575 },
    { PCI_VDEVICE(INTEL, E1000_DEV_ID_82576_QUAD_COPPER_ET2), board_82575 },
    { PCI_VDEVICE(INTEL, E1000_DEV_ID_82576_QUAD_COPPER), board_82575 },
    { PCI_VDEVICE(INTEL, E1000_DEV_ID_82575EB_COPPER), board_82575 },
    { PCI_VDEVICE(INTEL, E1000_DEV_ID_82575EB_FIBER_SERDES), board_82575 },
    { PCI_VDEVICE(INTEL, E1000_DEV_ID_82575GB_QUAD_COPPER), board_82575 },
    /* required last entry */
    {0, }
};

内核通过设备 ID 与驱动支持的设备列表匹配,选择合适的驱动控制网卡,然后调用之前注册到内核 PCI 子系统的探测函数(probe)完成初始化。
例如 igb 驱动程序的 igb_probe 函数,其处理流程包括:

  1. 设置 DMA 寻址限制和缓存一致性;
  2. 申请内核内存;
  3. struct net_device 结构体的创建、初始化和注册;
  4. 注册 struct net_device_ops(里面有 igb_open)到 net_device;
  5. 注册驱动支持的 ethtool 调用函数;
  6. 注册 poll 函数到 NAPI 子系统;

igb 驱动程序中 igb_probe 函数的部分代码如下:

// file: drivers/net/ethernet/intel/igb/igb_main.c
static int igb_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
{
    /* 申请 DMA 内存空间和 I/O 端口 */
    err = dma_set_mask(&pdev->dev, DMA_BIT_MASK(64));
    /* 获取 PCIe 设备的 Resource(Memory BAR、I/O BAR 和 MSI-X BAR),并通过这些 BARs 完成一系列访问和初始化 */
    err = pci_request_selected_regions(pdev, pci_select_bars(pdev, IORESOURCE_MEM), igb_driver_name);
    /* 网络设备 */
    netdev = alloc_etherdev_mq(sizeof(struct igb_adapter), IGB_MAX_TX_QUEUES);
    /* net_device_ops 结构体,代表一个网络设备 */
    netdev->netdev_ops = &igb_netdev_ops;
    /* 注册驱动支持的 ethtool 调用函数 */
    igb_set_ethtool_ops(netdev);
    /* 函数里面注册了 poll 函数 */
    err = igb_sw_init(adapter);
}

 

四、网卡设备的启用