20.PCI代码导读-5.驱动初始化流程-设备-《计算机知识》

admin 2025-11-02 22:26:01 系统网络 来源:ZONE.CI 全球网 0 阅读模式
  • PCI设备初始化流程
    • pci 主桥总线的初始化
    • pci_scan_child_bus 主桥子设备检测
    • pci_scan_slot(32个子设备扫描)
      • pci_acpi_init 初始化中断接口
      • pcibios_irq_init

    PCI设备初始化流程

    **

    pci 主桥总线的初始化

    1. 根BUS的创建!!!
    2. bus = pci_create_root_bus(NULL, busnum, ops->pci_ops,
    3. sysdata, &info->resources);
    4. 这里创建struct bus * bus ,其中
    5. bus->sysdata = bridge->sysdata;
    6. bus->msi = bridge->msi;
    7. bus->ops = bridge->ops;
    8. bus->number = bus->busn_res.start = bridge->busnr; // 就是当前主桥所管理的PCI BUS second.start,也就是起始总线

    **

    pci_scan_child_bus 主桥子设备检测

    关键点复述:我们这里复习 《PCI+Express体系结构》中了解到:一个PCI总线树(主桥)上,最多可以挂接256个PCI设备,包括PCI桥:

    • 每一条PCI总线,可以挂接32个PCI总线接口芯片(桥片)
    • 每个PCI设备都是通过一个PCI总线接口芯片连接到PCI主线上。
    • 每个PCI设备最多8个功能(0~7,在Linux下每一个都看做一个物理设备);
    • 这32个总线接口芯片(桥片)可以做成插槽,也可以直接在主板上。
    1. #define PCI_CONF1_ADDRESS(bus, devfn, reg) \
    2. (0x80000000 | ((reg & 0xF00) << 16) | (bus << 16) \
    3. | (devfn << 8) | (reg & 0xFC))

    所以CONFIG_ADDRESS[15:8] 是devfn,占8bit, 其中devfn[7:3]就是PCI总线接口芯片; devfn[2:0]是功能号。

    1. unsigned int pci_scan_child_bus(struct pci_bus *bus)
    2. {
    3. unsigned int devfn, pass, max = bus->busn_res.start; // bus号从当前主桥的起始bus号开始计算
    4. /* Go find them, Rover! 遍历一条PCI总线上的所有子总线,一条总线有32个接口,一个接口有8个子功能,所以这里只能以8递增 */
    5. for (devfn = 0; devfn < 0x100; devfn += 8) // 去遍历32个PCI总线设备- 同一个bus下最多支持 256个设备(32个总线接口芯片)
    6. pci_scan_slot(bus, devfn);
    7. max += pci_iov_bus_range(bus); // max 为总线最大个数,这里需要给sriov的vf预留足够的空间
    8. /*据说是需要调用两次pci_scan_bridge,第一次配置,第二次遍历*/
    9. for (pass = 0; pass < 2; pass++)
    10. list_for_each_entry(dev, &bus->devices, bus_list) {
    11. if (pci_is_bridge(dev))
    12. max = pci_scan_bridge(bus, dev, max, pass);
    13. }
    14. }

    pci_scan_slot(32个子设备扫描)

    1. pci_scan_slot(bus, devfn) // 注:这里的devfn=0,8,16,....,248
    2. dev = pci_scan_single_device(bus, devfn); //*** 超级重点*****,下边单独跟下这个函数
    3. for (fn = next_fn(bus, dev, 0); fn > 0; fn = next_fn(bus, dev, fn)) {
    4. // 如果桥片上的所有设备
    5. pci_scan_single_device(bus, devfn + fn);
    6. }
    7. // 这两部分都调用pci_scan_single_device 进行查找和初始化。
    8. pci_scan_single_device
    9. pci_get_slot(bus, devfn);
    10. pci_scan_device(bus, devfn); // *** 重点
    11. pci_device_add(dev, bus); // *** 重点
    12. pci_scan_device
    13. // 读取vendorID,并判断是否有效; (超时时间60*1000 )
    14. pci_bus_read_dev_vendor_id(bus, devfn, &l, 60*1000);
    15. // 分配struct pci_dev结构体
    16. struct pci_dev * dev = pci_alloc_dev(bus);
    17. // 初始化pci_dev
    18. pci_setup_device(dev)
    19. // 以总线号,设备号,功能号等方式来分配pci设备名称 pci_dev->device->init_name
    20. dev_set_name(&dev->dev, "%04x:%02x:%02x.%d",...);
    21. // 获取配置空间大小 ***重点函数之一
    22. dev->cfg_size = pci_cfg_space_size(dev);
    23. switch (dev->hdr_type) {
    24. case PCI_HEADER_TYPE_NORMAL:
    25. pci_read_irq(dev); // 读取irq信息
    26. pci_read_bases(dev, 6, PCI_ROM_ADDRESS); // 重点***资源分配管理
    27. ...
    28. break;
    29. case PCI_HEADER_TYPE_BRIDGE:
    30. ...
    31. break;
    32. .....
    33. }

    pci_read_bases相关概念:bar空间地址属性image.png

    1. pci_read_bases // 读取6个bar空间和rom资源函数
    2. for (pos = 0; pos < howmany; pos++) { // *********读取bar空间和资源分配*****
    3. struct resource *res = &dev->resource[pos];
    4. reg = PCI_BASE_ADDRESS_0 + (pos << 2);
    5. pos += __pci_read_base(dev, pci_bar_unknown, res, reg); // 重点
    6. }
    7. if (rom) { // rom信息,先跳过
    8. ......
    9. }
    10. __pci_read_base
    11. // 先读取地址数据,然后写全1,读取长度,写回地址数据
    12. pci_read_config_dword(dev, pos, &l);
    13. pci_write_config_dword(dev, pos, l | mask);
    14. pci_read_config_dword(dev, pos, &sz);
    15. pci_write_config_dword(dev, pos, l);
    16. if (type == pci_bar_unknown) {
    17. // 这里获取bar的属性:64bit/32bit,IO/MEM,PREFETCH等属性
    18. res->flags = decode_bar(dev, l);
    19. res->flags |= IORESOURCE_SIZEALIGN;
    20. }
    21. if (res->flags & IORESOURCE_MEM_64) { // 64-bit地址牵扯到地址和长度拼接,
    22. pci_read_config_dword(dev, pos + 4, &l);
    23. pci_write_config_dword(dev, pos + 4, ~0);
    24. pci_read_config_dword(dev, pos + 4, &sz);
    25. pci_write_config_dword(dev, pos + 4, l);
    26. l64 |= ((u64)l << 32);
    27. sz64 |= ((u64)sz << 32);
    28. mask64 |= ((u64)~0 << 32);
    29. }
    30. //*******总线地址--存储域地址转换部分
    31. /* 获取到资源起始地址和长度,分配总线地址属性 */
    32. region.start = l64;
    33. region.end = l64 + sz64 - 1;
    34. pcibios_bus_to_resource(dev->bus, res, &region);
    35. pcibios_resource_to_bus(dev->bus, &inverted_region, res)
    36. /*
    37. 注:window的offset才是 总线域到CPU域会一一映射,中间偏移window->offset
    38. */
    39. pcibios_bus_to_resource
    40. resource_list_for_each_entry(window, &bridge->windows) {
    41. // 主桥对应的存储区域范围 *** 参考主桥分配
    42. bus_region.start = window->res->start - window->offset;
    43. bus_region.end = window->res->end - window->offset;
    44. // 判断是物理地址区域
    45. if (region_contains(&bus_region, region)) {
    46. offset = window->offset;
    47. break;
    48. }
    49. }

    pci_bar_size如何计算pci_bar_size? 在vfio-mdev 的 mtty.c 中,有个模拟pci的设备,可以看到:mtty_create_config_space函数

    1. STORE_LE32((u32 *) &mdev_state->vconfig[0x10], 0x000000); // Memory空间
    2. mdev_state->bar_mask[0] = ~(MTTY_MMIO_BAR_SIZE) + 1; // 长度掩码空间
    3. pr_info("bar 0 mask is %#x\n",mdev_state->bar_mask[0]);

    那么问题来了, 比如 长度8的掩码 ~(MTTY_MMIO_BAR_SIZE) + 1; 是 0xFFFF_FFF8,是怎么计算出8的 ? 这里MTTY_MMIO_BAR_SIZE 肯定是2^n

    1. static u64 pci_size(u64 base, u64 maxbase, u64 mask)
    2. {
    3. u64 size = mask & maxbase; /* Find the significant bits */
    4. if (!size)
    5. return 0;
    6. /* Get the lowest of them to find the decode size, and
    7. from that the extent. */
    8. size = (size & ~(size-1)) - 1;
    9. /* base == maxbase can be valid only if the BAR has
    10. already been programmed with all 1s. */
    11. if (base == maxbase && ((base | size) & mask) != mask)
    12. return 0;
    13. return size;
    14. }

    可见, size = (size & ~(size-1)) - 1; 可以完美计算出真实size大小

    pci_cfg_space_size

    1. pci_cfg_space_size // 获取配置空间大小
    2. // 桥和pci-x暂时不考虑
    3. if (pci_is_pcie(dev)) // 根据前边获取到的信息,判断是否是pcie设备
    4. return pci_cfg_space_size_ext(dev); // 这里边就是尝试读取下0x100以后的数据,
    5. return PCI_CFG_SPACE_SIZE;

    pci_device_add

    1. pci_device_add
    2. pci_configure_device(dev); // 配置PCI设备
    3. pci_init_capabilities(dev); // capabilities初始化
    4. pci_msi_setup_pci_dev(dev);
    5. pci_configure_ari(dev);
    6. pci_iov_init(dev);

    pci_acpi_init 初始化中断接口

    1. pci_acpi_init
    2. pcibios_enable_irq = acpi_pci_irq_enable;
    3. pcibios_disable_irq = acpi_pci_irq_disable;
    4. x86_init.pci.init_irq = x86_init_noop;

    pcibios_irq_init

    01-shell脚本介绍-《shell脚本》 系统网络

    01-shell脚本介绍-《shell脚本》

    一、shell脚本是什么二、为什么要学shell,而不是其他计算机语言三、学习这门课程的优势四、学了能干什么五、学习什么内容六、学习的技巧七、成长路径八、学习环
    评论:0   参与:  15