iT邦幫忙

2024 iThome 鐵人賽

DAY 27
0
自我挑戰組

Linux Kernel 網路巡禮系列 第 27

27 - Intel igb 網卡驅動的初始化流程

  • 分享至 

  • xImage
  •  

接下來,我們將以 Intel 的 igb 網卡驅動程式為例,解析網卡驅動的工作原理。今天我們先探討乙太網路卡驅動如何發現網卡,並完成 net_device 的建立與初始化。

驅動程式如何發現網卡

首先,我們需要了解作業系統如何得知某張 PCIe 網卡應該使用哪個網卡驅動。根據 PCI(e) 設備的運作原理,PCI(e) 設備的 Configuration Space Header 保存了設備的基本資料,因此,當作業系統開機時,系統可以從這個 header 中取得 PCIe 網卡的 Vendor ID 及 Device ID。

// drivers/net/ethernet/intel/igb/igb_main.c
static const struct pci_device_id 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 },
	...
}

在驅動程式內部,會維護一個靜態列表 igb_pci_tbl,用來記錄該網卡驅動所支援的 PCIe 設備 (Vendor ID 和 Device ID)。

module_init(igb_init_module);

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

static struct pci_driver igb_driver = {
	.name     = igb_driver_name,
	.id_table = igb_pci_tbl, /* 支援列表 */
	.probe    = igb_probe,
	.remove   = igb_remove,
#ifdef CONFIG_PM
	.driver.pm = &igb_pm_ops,
#endif
	.shutdown = igb_shutdown,
	.sriov_configure = igb_pci_sriov_configure,
	.err_handler = &igb_err_handler
};

驅動載入時,會呼叫 pci_register_driver 函數向 kernel 的 PCI 子系統註冊,並提供所支援的 PCIe 設備列表 (igb_driver.id_table = igb_pci_tbl)。

當 PCI 子系統發現符合條件的網卡時,會呼叫該驅動程式的 pci_driver.probe 函數,也就是 igb_probe,請求驅動程式初始化該 PCIe 設備。

網卡設備的初始化

igb_probe 的初始化內容十分複雜,包含設定 MAC address、初始化網卡設備的記憶體空間等,本文將挑選部分內容進行說明。

igb_probe 中,首先會呼叫 alloc_etherdev_mq,為 net_device 分配記憶體空間:

static int igb_probe(struct pci_dev *pdev, const struct pci_device_id *ent) {
	struct net_device *netdev;
	struct igb_adapter *adapter;
	struct e1000_hw *hw;
	...
    netdev = alloc_etherdev_mq(sizeof(struct igb_adapter),
				   IGB_MAX_TX_QUEUES);
    ...
    adapter = netdev_priv(netdev);
	adapter->netdev = netdev;
	...
    netdev->netdev_ops = &igb_netdev_ops;
	igb_set_ethtool_ops(netdev);
    ...
    strcpy(netdev->name, "eth%d");
	err = register_netdev(netdev);
	...
}

// drivers/net/ethernet/intel/igb/igb.h
struct igb_adapter {
	unsigned long active_vlans[BITS_TO_LONGS(VLAN_N_VID)];

	struct net_device *netdev;
	...
}

alloc_etherdev_mq 負責分配 net_device 的記憶體空間。對於乙太網路卡,除了 net_device 本身的資訊之外,還需要額外保存物理網卡的資訊,因此,igb 驅動定義了一個 igb_adapter 結構來保存這些額外資訊。alloc_etherdev_mq 函數除了分配 net_device 的記憶體空間,還會分配 igb_adapter 的記憶體空間。

接著,驅動程式會填充 netdev_ops 以支援網路子系統所需的操作,同時透過 igb_set_ethtool_ops 填充 ethtool_ops 結構。我們平常使用 ethtool 操作乙太網路卡時,實際上就是在呼叫網卡驅動程式實作的 ethtool_ops

trcpy(netdev->name, "eth%d") 設定了網卡的名稱,因此,我們常見的網卡名稱會是 ethXXX 這樣的格式。最後,驅動會呼叫 register_netdev,向網路子系統註冊該網路設備。

記憶體映射操作

接下來,我們探討 igb_probe 中與記憶體映射相關的部分:

/* igb_probe */
err = pci_enable_device_mem(pdev);
err = pci_request_mem_regions(pdev, igb_driver_name);

驅動會呼叫 pci_enable_device_mem,如下所示:

/**
 * pci_enable_device_mem - Initialize a device for use with Memory space
 * @dev: PCI device to be initialized
 *
 * Initialize device before it's used by a driver. Ask low-level code
 * to enable Memory resources. Wake up the device if it was suspended.
 * Beware, this function can fail.
 */
pci_enable_device_mem(pdev);
- pci_enable_device_flags(dev, IORESOURCE_MEM);
-- pci_write_config_word(dev, PCI_COMMAND, cmd |= PCI_COMMAND_IO);

根據註解,在建立記憶體映射之前,應先呼叫這個函數。該函數會對 PCI configuration space header 中的 command register 寫入 PCI_COMMAND_IO (0x1)。根據 PCI 規範,這表示允許設備回應 Memory Space 存取操作。

接下來,驅動會呼叫 pci_request_mem_regions(pdev, igb_driver_name) 來標記記憶體映射區域:

pci_request_mem_regions(pdev, igb_driver_name)
- pci_request_selected_regions(pdev, pci_select_bars(pdev, IORESOURCE_MEM), name);
-- __pci_request_selected_regions(pdev, bars, res_name, 0); /* res_name = igb_driver_name */
--- __pci_request_region(pdev, i, res_name, excl)
---- __request_mem_region(pci_resource_start(pdev, bar),
					      pci_resource_len(pdev, bar), res_name,
					      exclusive)
----- __request_region_locked(res, parent, start, n, name, flags);
------ __request_resource(parent, res)

這個操作會將所有映射到記憶體的區塊標記成該驅動程式專用,以防止其他 kernel module 讀寫該區域。

/* igb_probe */
adapter->io_addr = pci_iomap(pdev, 0, 0);
netdev->mem_start = pci_resource_start(pdev, 0);
netdev->mem_end = pci_resource_end(pdev, 0);


最後,驅動會呼叫 pci_iomap 來將 Bar 0 映射到的物理記憶體空間,重新映射到虛擬記憶體空間中。

void __iomem *pci_iomap(struct pci_dev *dev, int bar, unsigned long maxlen)
{
	return pci_iomap_range(dev, bar, 0, maxlen);
}

void __iomem *pci_iomap_range(struct pci_dev *dev,
			      int bar,
			      unsigned long offset,
			      unsigned long maxlen)
{
	resource_size_t start = pci_resource_start(dev, bar);
	resource_size_t len = pci_resource_len(dev, bar);
	unsigned long flags = pci_resource_flags(dev, bar);
    ...
	if (flags & IORESOURCE_IO)
		return __pci_ioport_map(dev, start, len);
	if (flags & IORESOURCE_MEM)
		return ioremap(start, len);
    ...
    }

pci_iomap 函數會根據 BAR 的類型 (IO Space mapping 或 Memory Space mapping) 進行處理。對於 MMIO,會呼叫 ioremap 完成虛擬記憶體映射。


上一篇
Network Device 與 Socket Buffer
下一篇
Intel igb 網卡驅動與接收隊列
系列文
Linux Kernel 網路巡禮30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言