快速回忆
pci-ids 快速查询Mellanox PCIe的device的网址
PCIe设备id表:mlx5_core_pci_table
iseg是Initialization segment的意思,初始化字段,处于bar空间,BAR0的前几个字段,偏移是BAR0的0开始的。
背景
Mellanox网卡驱动主要是PCIe的驱动,驱动mlx5_core.ko主要负责PCIe驱动的管理,他会注册一个PCIe驱动到Linux内核的PCIe框架中,然后如果有PCIe设备热插拔或者rescan触发热插后就会调用。本文主要分析mlx5_core.ko如何利用PCIe的配置空间来获取他的Firmware版本号。
触发时机和关联key
首先,PCIe设备在BIOS阶段会被扫描枚举到PCIe设备并且放在ACPI(Advanced Configuration and Power Interface,高级配置与电源接口)模块中的acpi_pci中管理,并且会给PCIe设备根据bar空间的内容预留物理内存的地址空间。BIOS加载Linux系统会将相关资源信息高速Linux系统,并且PCIe设备就会在PCIe框架中创建对应设备,如果此时存在与之绑定的驱动,就会触发加载PCIe驱动的probe函数。其中PCIe驱动是根据pci_register_driver
的时候注册的驱动描述结构中的id_table(pci_device_id的结构)来决定该驱动绑定什么PCIe设备的。这里决定绑定的PCIe设备主要是通过PCIe设备的vendorID和deviceID。 这两个信息在PCIe设备中会存储在PCIe的配置空间中,在BIOS扫描阶段会读取这些配置信息并且存下来。
pci_device_id的结构:
struct pci_device_id {
__u32 vendor, device; /* Vendor and device ID or PCI_ANY_ID*/
__u32 subvendor, subdevice; /* Subsystem ID's or PCI_ANY_ID */
__u32 class, class_mask; /* (class,subclass,prog-if) triplet */
kernel_ulong_t driver_data; /* Data private to the driver */
__u32 override_only;
};
比如Mellanox的驱动注册几个关键信息:
#ifndef PCI_VENDOR_ID_MELLANOX
#define PCI_VENDOR_ID_MELLANOX 0x15b3
#endif
#define PCI_DEVICE_ID_MELLANOX_CONNECTX4_LX 0x1015
static const struct pci_device_id mlx5_core_pci_table[] = {
...
{ PCI_VDEVICE(MELLANOX, PCI_DEVICE_ID_MELLANOX_CONNECTX4_LX) },
...
}
static struct pci_driver mlx5_core_driver = {
.name = KBUILD_MODNAME,
.id_table = mlx5_core_pci_table,
.probe = probe_one,
...
}
pci_register_driver(&mlx5_core_driver);
可以看到这里pci注册驱动的结构中,就会有id_table,用来表示这个驱动支持哪些vendor和那些device。这里的PCIe的vendorID需要再PCI sig去注册获取。这里可以查询:pcisig,
也可以这里查询:PCIe vendor search
也可以这里查询:pci-ids
比如Mellanox查询出来的:https://admin.pci-ids.ucw.cz/read/PC/15b3
获取bar 0空间的物理地址
dev->bar_addr = pci_resource_start(pdev, 0); #获取PCIe设备的资源初始地址,bar0的。pci_resource_start 函数用于获取 PCI 设备资源的起始地址。
dev->iseg_base = dev->bar_addr; #iseg是Initialization segment的意思,初始化字段,处于bar空间,BAR0的前几个字段,偏移是BAR0的0开始的。
dev->iseg = ioremap(dev->iseg_base, sizeof(*dev->iseg)); #获取到物理地址后使用ioremap将IO地址空间映射到内核的虚拟地址空间。 后面驱动访问虚拟地址空间,会被转化为物理地址,并且是ioaddr,系统的host bridge访问该地址的时候发现是ioaddr,不会直接访问DDR,而是访问对应的设备。
根据iseg的虚拟地址直接访问获取系统信息
firmware version如何获取的?
mlx5_core_info(dev, "firmware version: %d.%d.%d\n", fw_rev_maj(dev),
fw_rev_min(dev), fw_rev_sub(dev));
static inline u16 fw_rev_maj(struct mlx5_core_dev *dev)
{
return ioread32be(&dev->iseg->fw_rev) & 0xffff;
}
static inline u16 fw_rev_min(struct mlx5_core_dev *dev)
{
return ioread32be(&dev->iseg->fw_rev) >> 16;
}
static inline u16 fw_rev_sub(struct mlx5_core_dev *dev)
{
return ioread32be(&dev->iseg->cmdif_rev_fw_sub) & 0xffff;
}
可以看到获取网卡的固件版本是通过访问iseg的fw_rev的地址的值,可以看到这个地址的值是在BAR0的第一个4Bytes里面。并且