pci总线初始化过程(linux-ag真人国际厅网站

// 声明:以下分析的代码,是针对i386 cpu的实现!!

pci_init()

 |

 |- pcibios_init()  // arch/i386/kernel/pci-pc.c !!

 |   |              // 早期,该函数完全通过调用bios接口实现,所以函数名中包含了"bios"

 |   |              // 后来,该函数演变为,可以通过config_pci_direct和config_pci_bios宏,选择调用bios接口,还是调用内核自己实现的接口

 |   |              // 而且两者不互斥,如果2个宏都打开,内核通常优先使用自己的接口,或用自己的接口,对bios接口的操作结果进行修正

 |   |

 |   |  // 获取bios提供的pci操作接口 (#ifdef config_pci_bios)

 |   |- pci_root_ops = pci_find_bios()

 |   |   |

 |   |   |  // 0xe0000~0xffff0为bios占用的物理内存

 |   |   |- for (check = (union bios32 *) __va(0xe0000); check <= (union bios32 *) __va(0xffff0); check)

 |   |       |

 |   |       |  // 每个bios32对象,都包含16字节头部,其中包含signature和checksum字段

 |   |       |- if (check->fields.signature != bios32_signature)

 |   |       |   |- continue

 |   |       |

 |   |       |  // 构造bios32对象时,通过设置checksum字段值,保证了整个对象占用的字节相加为0

 |   |       |- for (i = 0; i < length ; i)

 |   |       |   |- sum = check->chars[i]

 |   |       |- if (sum != 0)

 |   |       |   |- continue

 |   |       |

 |   |       |  // 进一步检查

 |   |       |- if (check_pcibios())

 |   |           |- if ((pcibios_entry = bios32_service(pci_service)))

 |   |           |   |- pci_indirect.address = pcibios_entry page_offset  // 设置bios的pci服务接口的段内偏移

 |   |           |   |- ...                                                 // 设置参数:pcibios_pci_bios_present

 |   |           |   |- lcall &pci_indirect                                 // 调用接口

 |   |           |             |- pci_indirect = { 0, __kernel_cs }         // 初始化时,已将段基址设置为__kernel_cs

 |   |           |- return &pci_bios_access

 |   |

 |   |  // 获取内核自己实现的pci操作接口 (#ifdef config_pci_direct)

 |   |- pci_root_ops = pci_check_direct()

 |   |   |

 |   |   |  // 探测主总线是否为pci总线

 |   |   |- if (pci_probe & pci_probe_conf1)

 |   |   |   |- outb (0x01, 0xcfb)            // 32位地址寄存器最高字节0xcfb,称为"pmc寄存器",它的bit1~7保留,bit0用于选择"pci配置访问机制"

 |   |   |   |                                // 参考:https://html.datasheetbank.com/datasheet-html/251876/intel/34page/290479-004.html (pci datasheet, page 34)

 |   |   |   |                                //       bit0=0 | pci配置访问机制0 | 2型pci设备

 |   |   |   |                                //      -------- ------------------ ------------

 |   |   |   |                                //       bit0=1 | pci配置访问机制1 | 1型pci设备

 |   |   |   |- tmp = inl (0xcf8)             // 从地址寄存器,读取原始值 (对于常规存储单元,先向a写入1(a=1),再读取a(b=a),结果会为1,但是pci配置寄存器往往不是这样)

 |   |   |   |- outl (0x80000000, 0xcf8)      // 向地址寄存器,写入0x80000000

 |   |   |   |                                // 至于为什么非要写入0x80000000进行检查,估计也是pci datasheet规定,后续分析不会过于纠结这种细节,建议尝试根据pci datasheet查找

 |   |   |   |

 |   |   |   |  // 读到的还是0x80000000(),并且通过pci_sanity_check()检查

 |   |   |   |- if (inl (0xcf8) == 0x80000000 && pci_sanity_check(&pci_direct_conf1))

 |   |   |   |   |                                |

 |   |   |   |   |                                |- for(dev.devfn=0; dev.devfn < 0x100; dev.devfn // 一条pci总线,最多可以有256个逻辑设备

 |   |   |   |   |                                    |- if ((!o->read_word(&dev, pci_class_device, &x) &&

 |   |   |   |   |                                        |   (x == pci_class_bridge_host || x == pci_class_display_vga)) ||

 |   |   |   |   |                                        |  (!o->read_word(&dev, pci_vendor_id, &x) &&

 |   |   |   |   |                                        |   (x == pci_vendor_id_intel || x == pci_vendor_id_compaq)))  // 连接了pci_class_bridge_host或pci_class_display_vga设备,认为是pci总线

 |   |   |   |   |                                        |                                                              // 连接的设备由intel或compaq厂商制造,也认为是pci总线

 |   |   |   |   |                                        |- return 1

 |   |   |   |   |

 |   |   |   |   |- outl (tmp, 0xcf8)                      // 为地址寄存器恢复原始值

 |   |   |   |   |- request_region(0xcf8, 8, "pci conf1"// 在内核i/o设备资源树增加一个节点,用于记录"宿主-pci桥"占用的i/o地址区间

 |   |   |   |   |- return &pci_direct_conf1               // 返回pci_direct_conf1对象地址,包含read_byte()、write_byte()等函数/pci操作列表

 |   |   |   |

 |   |   |   |- outl (tmp, 0xcf8)             // 为地址寄存器恢复原始值

 |   |   |

 |   |   |  // 探测主总线是否为isa总线 (现已不用)

 |   |   |- if (pci_probe & pci_probe_conf2)

 |   |       |- outb (0x00, 0xcfb)            // pmc寄存器=0,选择机制0,访问2型pci设备的配置寄存器

 |   |       |- ...

 |   |       |- return &pci_direct_conf2

 |   |

 |   |  // 扫描主总线,探测与枚举连接在主总线上的pci设备,如果设备为"pci-pci"桥,则递归扫描其连接的次层总线

 |   |  // 对于i386 cpu,必定已由bios在加电自检阶段枚举过一遍了,pci_scan_bus()相当于直接读取bios的设置成果,并分配管理对象进行记录

 |   |- pci_root_bus = pci_scan_bus(0, pci_root_ops, null)

 |   |   |

 |   |   |  // 分配pci_bus对象,用于记录总线信息

 |   |   |   |- if (pci_bus_exists(&pci_root_buses, bus))  // 检查pci_root_buses全局链表,是否已经存在总线号为bus的pci_bus对象

 |   |   |   |   |- return null

 |   |   |   |- b = pci_alloc_bus()                        // 分配pci_bus对象

 |   |   |   |- list_add_tail(&b->node, &pci_root_buses)   // 添加到pci_root_buses全局链表

 |   |   |   |- b->number = b->secondary = bus             // 记录总线号

 |   |   |   |                                             // number表示当前总线的编号

 |   |   |   |                                             // primary表示当前总线的上层总线编号

 |   |   |   |                                             // secondary表示总线所在pci桥的次层总线编号(也就是当前总线自己),所以总是等于number (额外搞个成员变量,估计是为了提高代码可读性)

 |   |   |   |  /* pci_dev对象的resource[device_count_resource]:

 |   |   |   |      pci设备本身占用:

 |   |   |   |       resource[0~5]:设备上可能有的内存地址区间

 |   |   |   |       resource[6]:设备上可能有的扩充rom区间

 |   |   |   |      pci桥是一种特殊的pci设备,额外包含次层总线占用的4个区间:

 |   |   |   |       resource[7]:i/o地址窗口

 |   |   |   |       resource[8]:内存地址窗口

 |   |   |   |       resource[9]:"可预取"内存地址窗口

 |   |   |   |       resource[10]:扩充rom区间窗口 */

 |   |   |   |  // pci_bus对象的*resource[4],分别指向所在pci桥的&resource[7~10]

 |   |   |   |- b->resource[0] = &ioport_resource   // resource[0]:可由当前总线使用的i/o地址区间 (由于是主总线,所以指向整个&ioport_resource)

 |   |   |   |- b->resource[1] = &iomem_resource    // resource[1]:可由当前总线使用的内存地址区间 (由于是主总线,所以指向整个&iomem_resource)

 |   |   |

 |   |   |  // 开始扫描

 |   |   |- if (b)

 |   |       |- b->ops = ops  // 次层总线的ops,同样设置为pci_root_ops

 |   |       |- b->subordinate = pci_do_scan_bus(b)  // 逐个发现连接在总线上的设备,为其建立pci_dev对象

 |   |           |- for (devfn = 0; devfn < 0x100; devfn = 8// 每次扫描1个pci插槽

 |   |           |   |- dev0.devfn = devfn

 |   |           |   |- pci_scan_slot(&dev0)

 |   |           |       |- for (func = 0; func < 8; func , temp->devfn // 每个pci设备,最多可以有8个功能,相当于8个逻辑设备 (逻辑设备号 = 5位设备号 3位功能号)

 |   |           |           |

 |   |           |           |- if (func && !is_multi)  // 0功能号,只在多功能设备中才需要处理

 |   |           |           |   |- continue            // 疑问:这里为什么不直接break??

 |   |           |           |

 |   |           |           |  // 读取"配置寄存器组"头部中的pci_header_type字段,根据它的最高位是否为1,判断设备是否为单功能,从而决定下一轮循环是否需要继续

 |   |           |           |- pci_read_config_byte(temp, pci_header_type, &hdr_type)  // pci_read_config_byte()函数,根据pci_op(read, byte, u8 *)宏展开定义

 |   |           |           |   |- dev->bus->ops->read_byte()  // ops通常为pci_direct_conf1 (见pcibios_init()入口,对pci_root_ops的设置)

 |   |           |           |       |

 |   |           |           |       |- pci_conf1_read_config_byte()            // 如果ops=&pci_direct_conf1

 |   |           |           |       '   |- outl(config_cmd(dev,where), 0xcf8// 按照pci规范,向地址寄存器(0xcf8),写入"总线号,逻辑设备号,寄存器偏移(where)"

 |   |           |           |       '   |        |- (0x80000000 | (dev->bus->number << 16) | (dev->devfn << 8) | (where & ~3))

 |   |           |           |       '   |- *value = inb(0xcfc (where&2))     // 从数据寄存器(0xcfc),读取设备的pci_header_type寄存器值

 |   |           |           |       '

 |   |           |           |       '- pci_bios_read_config_byte()             // 如果ops=&pci_bios_access

 |   |           |           |           |- ...                                 // 设置参数:pcibios_read_config_word

 |   |           |           |           |- lcall &pci_indirect                 // 调用bios的pci服务接口 (段基址、偏移,已分别由初始化和check_pcibios()函数设置)

 |   |           |           |

 |   |           |           |- temp->hdr_type = hdr_type & 0x7f  // 7位代表设备类型:

 |   |           |           |                                    // pci_header_type_normal(普通pci设备)、pci_header_type_bridge(pci-pci桥)、pci_header_type_cardbus(pci-cardbus桥)

 |   |           |           |

 |   |           |           |- dev = pci_scan_device(temp)       // 读取逻辑设备的出厂信息

 |   |           |           |   |                                // "配置寄存器组",一方面提供软件对设备的配置界面,另一方面,携带设备的出厂信息

 |   |           |           |   |  // 读取"配置寄存器组"头部中的pci_header_type字段,低16位为厂商编号,高16位为设备编号

 |   |           |           |   |- pci_read_config_dword(temp, pci_vendor_id, &l)

 |   |           |           |   |- if (l == 0xffffffff || l == 0x00000000 || l == 0x0000ffff || l == 0xffff0000// 0或全1,都为无效的厂商编号、备编号

 |   |           |           |   |   |- return null

 |   |           |           |   |- dev = kmalloc(sizeof(*dev), gfp_kernel)  // 分配pci_dev对象

 |   |           |           |   |- ...                                      // 初始化pci_dev对象,设置厂商编号、设备编号

 |   |           |           |   |- pci_setup_device(dev)                    // 根据设备类型,进一步填充pci_dev对象

 |   |           |           |       |

 |   |           |           |       |  // 读取"配置寄存器组"头部中的pci_class_revision字段

 |   |           |           |       |- pci_read_config_dword(dev, pci_class_revision, &class// 相比表示大类的hdr_type,class对设备类型进一步细分,其中包含:

 |   |           |           |       |                                                          // pci_class_device(高16位)、pci_class_prog(次低8位)、pci_revision_id(低8位)

 |   |           |           |       |- switch (dev->hdr_type)

 |   |           |           |           |

 |   |           |           |           |- case pci_header_type_normal      // 普通pci设备

 |   |           |           |           |   |

 |   |           |           |           |   |- if (class == pci_class_bridge_pci)

 |   |           |           |           |   |   |- goto bad                 // 可以看出,只要不是"pci-pci"桥,都属于0型设备

 |   |           |           |           |   |

 |   |           |           |           |   |  // 读取"配置寄存器组"头部中的pci_interrupt_pin和pci_interrupt_line字段

 |   |           |           |           |   |- pci_read_irq(dev)

 |   |           |           |           |   |   |  // pci_interrupt_pin字段,表示设备与pci插槽之间,中断请求信号线的连接关系 (由硬件决定,只读)

 |   |           |           |           |   |   |  // 0:表示设备不产生中断请求; 1~4:分别表示设备与pci插槽的inta~intd中断请求信号线连接

 |   |           |           |           |   |   |- pci_read_config_byte(dev, pci_interrupt_pin, &irq)

 |   |           |           |           |   |   |- if (irq)

 |   |           |           |           |   |   |   |  // pci_interrupt_line字段,表示设备经过pci插槽,最终连接了中断控制器的哪根中断请求信号线 (由软件设置)

 |   |           |           |           |   |   |   |  // pci_interrupt_line字段只起记录作用,不起控制作用,修改其值,并不会修改连接关系,不过连接关系通常也可以由软件设置

 |   |           |           |           |   |   |   |  // 如果这时读取的irq不为0,一定是因为已由bios设置,内核后续不会对其进行修改

 |   |           |           |           |   |   |   |- pci_read_config_byte(dev, pci_interrupt_line, &irq)

 |   |           |           |           |   |   |

 |   |           |           |           |   |   |- dev->irq = irq  // 记录到pci_dev对象 (对于irq为0的情况,会在后续调用pcibios_lookup_irq()函数时,进行处理)

 |   |           |           |           |   |

 |   |           |           |           |   |  // 读取"配置寄存器组"头部中的pci_base_address_0~pci_base_address_5和pci_rom_address字段

 |   |           |           |           |   |- pci_read_bases(dev, 6, pci_rom_address)

 |   |           |           |           |   |   |

 |   |           |           |           |   |   |  // pci设备中,通常包含寄存器或ram"(可挥发)功能存储区间"(最多6个),pci_base_address_0~pci_base_address_5寄存器,用于记录它们的出厂信息,以及配置它们的映射地址

 |   |           |           |           |   |   |- for(pos=0; pos= next)

 |   |           |           |           |   |   |   |

 |   |           |           |           |   |   |   |  // 对于已由bios配置过的寄存器,内核后续不会对其进行修改,只会在检查到配置错误的情况下,对其进行修正

 |   |           |           |           |   |   |   |- reg = pci_base_address_0 (pos << 2) // pci_base_address_0~pci_base_address_5寄存器,长度都为4字节

 |   |           |           |           |   |   |   |- pci_read_config_dword(dev, reg, &l)   // 读取区间起始地址

 |   |           |           |           |   |   |   |- pci_write_config_dword(dev, reg, ~0// 写入全1 (根据pci规范)

 |   |           |           |           |   |   |   |- pci_read_config_dword(dev, reg, &sz)  // 读取区间大小

 |   |           |           |           |   |   |   |- pci_write_config_dword(dev, reg, l)   // 恢复原始值

 |   |           |           |           |   |   |   |

 |   |           |           |           |   |   |   |  // 起始地址,bit0=0,表示内存地址区间,bit0=1表示i/o地址区间

 |   |           |           |           |   |   |   |  /* 区间类型由映射到什么区间决定,而跟存储单元是什么介质(内存/寄存器),以及所在的硬件设备无关

 |   |           |           |           |   |   |   |     linux内核通常优先选择使用内存空间:

 |   |           |           |           |   |   |   |     1. 有些cpu没有i/o指令,因而没有i/o空间,寄存器也就都在存储器地址空间中

 |   |           |           |           |   |   |   |     2. 有些cpu有i/o指令,但是指令的寻址空间很小,比如i386 cpu的i/o指令,操作数最多16位,即i/o空间为64kb,非常拥挤

 |   |           |           |           |   |   |   |     3. c语言没有访问i/o地址空间的语言成分,需要调用汇编子过程,不方便而且效率也稍低 */

 |   |           |           |           |   |   |   |- if ((l & pci_base_address_space) == pci_base_address_space_memory)

 |   |           |           |           |   |   |   |   |

 |   |           |           |           |   |   |   |   |  // 内存区间,起始地址低4位为控制信息:

 |   |           |           |           |   |   |   |   |  // bit0:区间类型 (0: 内存区间; 1: i/o区间)

 |   |           |           |           |   |   |   |   |  // bit1:区间大小是否超过1mb (0: 未超过; 1: 超过)

 |   |           |           |           |   |   |   |   |  // bit2: 区间地址位数 (0: 32位; 1: 64位)

 |   |           |           |           |   |   |   |   |  // bit3: 区间是否"可预取" (0: 否; 1:是)

 |   |           |           |           |   |   |   |   |  //       对于普通的内存单元,读操作是不会修改其内容的,但是如果内存区间是由寄存器区间映射的,内容就有可能被读操作修改:

 |   |           |           |           |   |   |   |   |  //       1. 比如fifo寄存器,读走队列头的元素后,下一个元素就成为首个元素,存入fifo寄存器了

 |   |           |           |           |   |   |   |   |  //       2. 有些状态寄存器,状态被读出之后,寄存器就会硬件自动清0

 |   |           |           |           |   |   |   |   |  //       "预读"是指,程序在读第n个单元时,cpu会把第n 1个单元也读入缓存:

 |   |           |           |           |   |   |   |   |  //       对内容可能被读操作修改的存储单元,进行"预读",就会存在问题:

 |   |           |           |           |   |   |   |   |  //       比如,程序在读第n个单元时,cpu会把第n 1个单元也读入缓存,但在缓存被冲刷掉之前,程序并没有取走第n 1个单元,程序后面再想重新读取时,内存单元却已经被"预读"修改了

 |   |           |           |           |   |   |   |   |  //       所以,通常将映射到内存空间的寄存器区间,"可预取"标志置为0

 |   |           |           |           |   |   |   |   |- res->start = l & pci_base_address_mem_mask

 |   |           |           |           |   |   |   |   |

 |   |           |           |           |   |   |   |   |  // pci规范规定,高28位中,只有位置最低的1,用于表示区间大小,而出厂值还有其它位为1,是为了表示哪些位可以由软件设置,所以不能忽略

 |   |           |           |           |   |   |   |   |- sz = pci_size(sz, pci_base_address_mem_mask)

 |   |           |           |           |   |   |   |       |

 |   |           |           |           |   |   |   |       |  // 假设:size = x100

 |   |           |           |           |   |   |   |       |  // 则:size-1 = x100-1 = x011

 |   |           |           |           |   |   |   |       |  //     => ~(size-1) = x100  (其中:x & x = 0)

 |   |           |           |           |   |   |   |       |  //                  & x100  (size)

 |   |           |           |           |   |   |   |       |  //                 --------

 |   |           |           |           |   |   |   |       |  //                  = 0100  (高位x不管为多少,都转换为0了)

 |   |           |           |           |   |   |   |       |- size = size & ~(size-1)

 |   |           |           |           |   |   |   |       |- return size-1

 |   |           |           |           |   |   |   |- else

 |   |           |           |           |   |   |   |   |

 |   |           |           |           |   |   |   |   |  // i/o区间,起始地址低3位为控制信息

 |   |           |           |           |   |   |   |   |- res->start = l & pci_base_address_io_mask

 |   |           |           |           |   |   |   |   |- sz = pci_size(sz, pci_base_address_io_mask & 0xffff)

 |   |           |           |           |   |   |   |

 |   |           |           |           |   |   |   |- res->end = res->start (unsigned long) sz

 |   |           |           |           |   |   |   |- ... // 暂不关心64位地址的情况

 |   |           |           |           |   |   |

 |   |           |           |           |   |   |  // pci设备中,有时也会包含rom"(不可挥发)功能存储区间"(最多1个),pci_rom_address寄存器,用于记录它的出厂信息,以及配置它的映射地址

 |   |           |           |           |   |   |- if (rom)

 |   |           |           |           |   |       |- ... // 和pci_base_address_0~pci_base_address_5处理过程类似

 |   |           |           |           |   |

 |   |           |           |           |   |  // 读取其它字段

 |   |           |           |           |   |- pci_read_config_word(dev, pci_subsystem_vendor_id, &dev->subsystem_vendor)

 |   |           |           |           |   |- pci_read_config_word(dev, pci_subsystem_id, &dev->subsystem_device)

 |   |           |           |           |

 |   |           |           |           |- case pci_header_type_bridge      // pci-pci桥

 |   |           |           |           |   |

 |   |           |           |           |   |- if (class != pci_class_bridge_pci)

 |   |           |           |           |   |   |- goto bad                 // 可以看出,仅"pci-pci"桥,属于1型设备

 |   |           |           |           |   |

 |   |           |           |           |   |  // 相比普通pci设备,很多配置寄存器在pci-pci桥中不存在,并且pci-pci桥上,最多只有2个可挥发存储区间

 |   |           |           |           |   |- pci_read_bases(dev, 2, pci_rom_address1)  // 不过次层pci总线,需要后续进行递归扫描

 |   |           |           |           |

 |   |           |           |           |- case pci_header_type_cardbus     // pci-cardbus桥 (专用于笔记本,暂不关心)

 |   |           |           |               |- ...

 |   |           |           |

 |   |           |           |- pci_name_device(dev)  // 根据厂商和设备编号,获取厂商和设备名称 (转换表存储于drivers/pci/pci.ids文件)

 |   |           |           |

 |   |           |           |- if (!func)

 |   |           |           |   |- is_multi = hdr_type & 0x80                  // 是否多功能

 |   |           |           |- list_add_tail(&dev->global_list, &pci_devices)  // 将pci_dev对象添加到pci_devices全局链表

 |   |           |           |- list_add_tail(&dev->bus_list, &bus->devices)    // 将pci_dev对象添加到所属pci_bus的devices链表

 |   |           |           |

 |   |           |           |  // 有些设备出售后,厂商却发现固化在"配置寄存器组"中的初始化信息有问题,只好提供修正代码,在内核层面进行修改

 |   |           |           |- pci_fixup_device(pci_fixup_header, dev)

 |   |           |               |

 |   |           |               |  // 数组元素表示:对于由vendor厂商提供的device设备,在pass阶段,执行hook()修正函数

 |   |           |               |  /* pcibios_fixups[]:不同cpu架构,使用各自的

 |   |           |               |     pci_fixups[]:所有cpu架构共用

 |   |           |               |     都是pci_fixup对象全局数组,以{0}元素结束 */

 |   |           |               |  // 疑问:如果存在pci bios,加电自检阶段,就已经基于错误的出厂信息,枚举过设备了,到内核执行阶段才修复,来得及吗??

 |   |           |               |- pci_do_fixups(dev, pass, pcibios_fixups)

 |   |           |               |- pci_do_fixups(dev, pass, pci_fixups)

 |   |           |                   |

 |   |           |                   |  // dev包含vendor和device信息

 |   |           |                   |  // pass为pci_fixup_header,表示"配置寄存器组"头部信息读出以后,pci_fixup_final表示全部信息读出以后

 |   |           |                   |- while (f->pass)

 |   |           |                       |- if (f->pass == pass &&

 |   |           |                       |   |  (f->vendor == dev->vendor || f->vendor == (u16) pci_any_id) &&

 |   |           |                       |   |  (f->device == dev->device || f->device == (u16) pci_any_id))

 |   |           |                       |   |

 |   |           |                       |   |- f->hook(dev)  // pass,vendor,device全部命中,执行对应的修正函数

 |   |           |                       |- f

 |   |           |

 |   |           |  // 不光pci设备中的出厂信息会有错,有些母板也存在设计问题

 |   |           |- pcibios_fixup_bus(bus)

 |   |           |   |

 |   |           |   |  // 去除"幻影" (有些母板上,会在两个不同的综合地址中,读出同一设备的头部信息,从而造成重复枚举,在形成的pci树中引入"幻影")

 |   |           |   |- pcibios_fixup_ghosts(b)

 |   |           |   |

 |   |           |   |  // pci_scan_slot()函数,只读取了pci设备"功能存储区间"的信息,还没读取pci桥为次层总线保存的地址窗口

 |   |           |   |- pci_read_bridge_bases(b)

 |   |           |       |

 |   |           |       |  /* pci桥是一种特殊的pci设备,本身并不一定有ram和rom,但是要有3个地址窗口 (也用resource对象描述):

 |   |           |       |     1. i/o地址窗口 (窗口起始地址和大小,都以4kb对齐,pci_io_base和pci_io_limit低4位全0时,表示i/o地址为16位,全1表示i/o地址为32位)

 |   |           |       |        pci_io_base(8位): i/o起始地址(16位) = pci_io_base高4 12*0

 |   |           |       |        pci_io_limit(8位): i/o结束地址(16位) = pci_io_limit高4 12*1

 |   |           |       |        pci_io_base_upper16(16位): i/o起始地址(32位) = pci_io_base_upper16 pci_io_base高4 12*0

 |   |           |       |        pci_io_limit_upper16(16位): i/o结束地址(32位) = pci_io_limit_upper16 pci_io_limit高4 12*1

 |   |           |       |     2. 存储器地址窗口 (窗口起始地址和大小,都以1mb对齐,主要用于映射在存储器地址空间的i/o寄存器,最低4位固定为0)

 |   |           |       |        pci_memory_base(16位): 存储器起始地址(32位) = pci_memory_base高12 20*0

 |   |           |       |        pci_memory_limit(16位): 存储器结束地址(32位) = pci_memory_base高12 20*1

 |   |           |       |     3. "可预取"存储器地址窗口 (窗口起始地址和大小,都以1mb对齐,最低4位为0表示地址为32位,为1表示地址为64位)

 |   |           |       |        pci_pref_memory_base(16位): "可预取"存储器起始地址(32位) = pci_pref_memory_base高12 20*0

 |   |           |       |        pci_pref_memory_limit(16位): "可预取"存储器结束地址(32位) = pci_pref_memory_limit高12 20*1

 |   |           |       |        pci_pref_base_upper32(32位): "可预取"存储器起始地址(64位) = pci_pref_base_upper32 pci_pref_memory_base高12 20*0

 |   |           |       |        pci_pref_limit_upper32(32位): "可预取"存储器结束地址(64位) = pci_pref_base_upper32 pci_pref_memory_limit高12 20*1

 |   |           |       |     cpu一侧访问的地址,必须在某个窗口之内(位于当前总线),才能到达下游,设备一侧访问的地址,必须在所有窗口之外(不在当前总线),才能到达上游

 |   |           |       |     此外,pci命令寄存器("配置寄存器组"头部中的pci_command字段),包含"memory access enable""i/o access enable"两个控制位

 |   |           |       |     控制位为0时,窗口对2个方向都关闭,用于保证还没为pci设备各个区间分配合适的总线地址时,cpu和设备两侧不会相互干扰 */

 |   |           |       |- for(i=0; i<3; i )

 |   |           |       |   |- child->resource[i] = &dev->resource[pci_bridge_resources i]  // pci_bus对象中的resource只是指针,指向所在pci桥的pci_dev对象的resource

 |   |           |       |

 |   |           |       |  // 读取pci_io_base,pci_memory_base,pci_pref_memory_base等寄存器,记录到pci_bus对象的resource[0~2]

 |   |           |       |  // 对于i386 cpu,pci桥的地址窗口,必然已在加电自检阶段,就由bios根据次层总线的需要,设置好了!!

 |   |           |       |- ...

 |   |           |

 |   |           |  // 递归扫描pci-pci桥或pci-cardbus桥上的次层总线

 |   |           |- for (pass=0; pass < 2; pass // 第一趟针对已由bios处理过的pci桥

 |   |               |                              // 第二趟针对未经bios处理过的pci桥 (疑问:对于i386 cpu,为什么会存在这种情况??)

 |   |               |- for (ln=bus->devices.next; ln != &bus->devices; ln=ln->next// 遍历上层总线上面的pci设备

 |   |                   |- dev = pci_dev_b(ln)

 |   |                   |- if (dev->hdr_type == pci_header_type_bridge || dev->hdr_type == pci_header_type_cardbus)  // 设备为"pci-pci""pci-cardbus"

 |   |                       |

 |   |                       |  //  pci_scan_bridge()函数写的有点绕:

 |   |                       |  //                       | if ((buses & 0xffff00)) | pass | run    | 目的

 |   |                       |  // ---------------------- ------------------------- ------ -------- ------------------------------------------------------

 |   |                       |  //  已由bios处理,第一趟 |            y            |  0   | 772行  | 第一趟仅为已由bios枚举的总线分配pci_bus对象,并统计最大总线号max

 |   |                       |  //  未经bios处理,第一趟 |            n            |  0   | return | 等待第一趟扫描完毕,max值完全确认后,在第二趟扫描中处理

 |   |                       |  //  已由bios处理,第二趟 |            y            |  1   | return | 已经在第一趟扫描中处理过了

 |   |                       |  //  未经bios处理,第二趟 |            n            |  1   | 792行  | 基于已由bios处理的最大总线号max,处理未经bios处理的总线

 |   |                       |- max = pci_scan_bridge(bus, dev, max, pass)

 |   |                           |

 |   |                           |  // 读取"配置寄存器组"头部中的pci_primary_bus字段 (byte0:主总线号; byte1:次总线号; byte2:子树中最大总线号)

 |   |                           |- pci_read_config_dword(dev, pci_primary_bus, &buses)

 |   |                           |

 |   |                           |  // 次总线号和子树中最大总线号不全为0,表示加电自检阶段,bios已经枚举过当前总线,在此之前就已经设置过了

 |   |                           |- if ((buses & 0xffff00) && !pcibios_assign_all_busses())

 |   |                           |   |                          |- 空操作

 |   |                           |   |- if (pass)   // 已由bios枚举的总线,第二趟扫描不需要处理

 |   |                           |   |   |- return max

 |   |                           |   |

 |   |                           |   |- child = pci_add_new_bus(bus, dev, 0// 为新探测到的总线,分配和设置pci_bus对象 (逻辑简单,不再展开)

 |   |                           |   |- ...                                   // 继续设置pci_bus对象

 |   |                           |   |- if (!is_cardbus)

 |   |                           |   |   |- cmax = pci_do_scan_bus(child)     // 递归调用 (遇到次层总线,立即递归扫描,而不是探测到上层总线的所有次层总线后,再依次扫描次层总线,所以是深度优先)

 |   |                           |   |   |- if (cmax > max) max = cmax

 |   |                           |   |- else

 |   |                           |       |- cmax = child->subordinate         // pci-cardbus桥不支持次层总线

 |   |                           |       |- if (cmax > max) max = cmax

 |   |                           |- else

 |   |                           |   |- if (!pass// 未经bios处理的总线,等待第一趟扫描完毕

 |   |                           |   |   |- return max

 |   |                           |   |

 |   |                           |   |  // 未经bios处理的pci桥,相关配置寄存器,就得由内核设置 (读写过程涉及pci规范)

 |   |                           |   |- pci_read_config_word(dev, pci_command, &cr)

 |   |                           |   |- pci_write_config_word(dev, pci_command, 0x0000)

 |   |                           |   |- pci_write_config_word(dev, pci_status, 0xffff)

 |   |                           |   |- child = pci_add_new_bus(bus, dev, max)             // 为新探测到的总线,分配和设置pci_bus对象

 |   |                           |   |- buses = (buses & 0xff000000)

 |   |                           |   |        | ((unsigned int)(child->primary)     <<  0)

 |   |                           |   |        | ((unsigned int)(child->secondary)   <<  8)

 |   |                           |   |        | ((unsigned int)(child->subordinate) << 16)   // 按照pci_primary_bus寄存器的组成规范,进行拼接

 |   |                           |   |- pci_write_config_dword(dev, pci_primary_bus, buses)  // 将总线号写入pci_primary_bus寄存器

 |   |                           |   |- if (!is_cardbus)

 |   |                           |   |   |- max = pci_do_scan_bus(child)                     // 递归调用

 |   |                           |   |- else

 |   |                           |   |   |- max = 3

 |   |                           |   |- child->subordinate = max

 |   |                           |   |- pci_write_config_byte(dev, pci_subordinate_bus, max)

 |   |                           |   |- pci_write_config_word(dev, pci_command, cr)

 |   |                           |

 |   |                           |- return max

 |   |

 |   |  /* 到此为止,已经为0号主总线及其所有子总线分配了管理对象,并且获取了出厂信息,接下来继续完成如下操作:

 |   |     1. 设备中断请求线与中断控制器的连接

 |   |     2. 设备各个存储器区间,与总线地址的映射 (对于i386 cpu,一定已在加电自检阶段,由bios完成了映射,接下来只需检查并修正)

 |   |     另外,母板可以有多个"宿主-pci桥",从而可以接多条pci主总线,然而以上过程只完成了0号pci主总线树的枚举,因此,接下来还要完成其它主总线树的枚举和设置 */

 |   |

 |   |  // 确认pci设备与中断控制器之间的连接,记录到pci_interrupt_line寄存器 (将来为设备设置中断处理函数时,需要知道登记到哪个中断服务队列)

 |   |  /* 参照"外设-pci插槽-路径互连器-中断控制器"之间的中断请求线连接关系图:

 |   |     "外设-pci插槽"之间:由硬件决定,记录在pci设备的pci_interrupt_pin寄存器,以上过程已经读取了 (1~4分别表示与inta~intd连接)

 |   |     "pci插槽-路径互连器"之间:由母板设计决定,为此,bios提供了一个"中断请求路径表"(irq_routing_table对象),记录每个pci插槽的inta~intd中断请求线,分别与互连器的哪根请求线连接

 |   |     "路径互连器-中断控制器"之间:部分由硬件设计决定,其余由软件设置 (使得内核可以将中断设备,均匀分布到中断控制器的引脚上) */

 |   |- pcibios_irq_init()

 |   |   |

 |   |   |  // irq_routing_table对象的slots[]数组,记录了各个设备所在pci插槽的inta~intd,与互连器的哪根中断请求线连接,以及互连器的这根请求线,可以连接中断控制器的哪些线

 |   |   |- pirq_table = pirq_find_routing_table()  // 利用内核自身接口,定位bios中的"中断请求路径表"

 |   |   |   |- for(addr = (u8 *) __va(0xf0000); addr < (u8 *) __va(0x100000); addr = 16)

 |   |   |       |- ...      // 根据signature和checksum等特征,寻找irq_routing_table对象位置

 |   |   |- if (!pirq_table && (pci_probe & pci_bios_irq_scan))

 |   |   |   |- pirq_table = pcibios_get_irq_routing_table()  // #ifdef config_pci_bios (利用bios接口,定位bios中的"中断请求路径表")

 |   |   |

 |   |   |  // 根据"中断请求路径表"信息,查询"pci设备-中断控制器"连接关系:

 |   |   |  //         硬件决定         中断请求路径表            部分由硬件决定,其余由软件设置

 |   |   |  // pci设备----------pci插槽----------------路径互连器--------------------------------中断控制器

 |   |   |- if (pirq_table)  // 疑问:如果bios中找不到"中断请求路径表",为什么不返回错误??

 |   |       |               // 这种情况一定意味着,不需要内核设置"路径互连器-中断控制器"之间的连接(已完全由硬件决定),相应的,bios阶段就能完全确定"pci设备-中断控制器"之间的连接关系。

 |   |       |

 |   |       |  // 先额外搞点别的事

 |   |       |  // 调用pcibios_irq_init()之前,只完成了0号pci主总线树的枚举,pirq_peer_trick()根据irq_routing_table对象中出现的其它总线号,进行补充枚举

 |   |       |- pirq_peer_trick()

 |   |       |   |

 |   |       |   |  // 每个中断路径表项对应pci插槽所在的总线,一定是经过bios确认存在的,并且每一条都有可能是主总线

 |   |       |   |- for(i=0; i < (rt->size - sizeof(struct irq_routing_table)) / sizeof(struct irq_info); i )

 |   |       |   |   |- e = &rt->slots[i]

 |   |       |   |   |- busmap[e->bus] = 1

 |   |       |   |

 |   |       |   |  /* 疑问1:busmap[]数组,不是0~pcibios_last_bus下标处必然为1吗??

 |   |       |   |     个人猜测,bios并不会为空pci插槽分配slots[i]对象,此处是为了只对插了pci设备的总线,调用pci_scan_bus(),进而分配总线管理对象

 |   |       |   |     后面调用的pcibios_fixup_peer_bridges()函数(穷举式扫描总线),相比此处,正是直接通过pcibios_last_bus遍历,但是多了"总线上是否有pci设备"的判断

 |   |       |   |    

 |   |       |   |     疑问2:根据《linux内核源代码情景分析》说明,以下过程是为了补充扫描其它pci主总线树,但是《pci express体系结构导读》(王齐),2.4.3节提到:

 |   |       |   |            "当处理器系统中存在多个host主桥时,将有多个编号为0的pci总线,但是这些编号为0的pci总线分属不同的pci总线域"

 |   |       |   |     然而从此处看,linux-2.4.0内核,并没有对pci总线分域,并且期望的是:第n 1条pci主总线的编号 = 第n条pci主总线树中最大总线编号 1

 |   |       |   |     为了求证这一点,我又粗略看了《存储技术原理分析:基于linux_2.6内核源代码》,从linux-2.6.34内核源码中,看到如下片段:

 |   |       |   |            pirq_peer_trick()  // arch/x86/pci/irq.c

 |   |       |   |             |- ...

 |   |       |   |             |- for (i = 1; i < 256; i )

 |   |       |   |                 |- if (!busmap[i] || pci_find_bus(0, i))  // 如果已经存在于domain0,不再重复扫描

 |   |       |   |                 |   |- continue                           // 推测:linux-2.6.34内核即使对pci总线进行了分域,也没有期望每条pci主总线,从0开始编号

 |   |       |   |                 |- pci_scan_bus_on_node()

 |   |       |   |                     |- pci_scan_bus()

 |   |       |   |                         |- pci_scan_bus_parented()

 |   |       |   |                             |- pci_create_bus()

 |   |       |   |                                 |- pci_find_bus(pci_domain_nr(b), bus)

 |   |       |   |                                                  |- struct pci_sysdata *sd = bus->sysdata

 |   |       |   |                                                  |- return sd->domain

 |   |       |   |     结论:linux-2.4.0内核非常老,有些实现细节会跟较新的设计思路不一致,不用过于纠结。

 |   |       |   |

 |   |       |   |     疑问30号总线除外,总线要先编号,才能被访问(可以认为0号总线在一个固定位置,不需要编号),既然编号前不能访问,又怎么为它编号呢??

 |   |       |   |     对总线进行编号,只是设置总线所在pci桥的pci_secondary_bus寄存器,并不需要访问总线本身(总线本身也没有寄存器啥的)

 |   |       |   |     所以,0号之外的主总线,虽然编号不是固定的,但是所在"宿主-pci"桥在母板的位置是固定的,可以随时对它编号并访问  */

 |   |       |   |

 |   |       |   |  // 扫描其它pci主总线树 (根据明确的总线清单进行补漏,否则后期需要执行pcibios_fixup_peer_bridges(),进行低效的穷举式扫描)

 |   |       |   |- for(i=1; i<256; i )

 |   |       |       |

 |   |       |       |- if (busmap[i])

 |   |       |       |   |

 |   |       |       |   |  // 对源码中注释的解释:

 |   |       |       |   |  // 由于总线编号,采用的是深度优先遍历方式,那么次层总线号必然大于所在主总线号,所以如果i是次层总线,则所在主总线一定会早于i被扫描,i总线也在那时就已经扫描了

 |   |       |       |   |  // 所以pci_scan_bus()返回不为空时,i一定为pci主总线

 |   |       |       |   |- pci_scan_bus(i, pci_root_bus->ops, null)  // 如果i总线之前已经扫描,pci_scan_bus()会保证不对其重复分配pci_dev对象并扫描

 |   |       |       |

 |   |       |       |- pcibios_last_bus = -1  // pcibios_last_bus表示加电阶段,bios设置的最大总线编号,此处设置为-1,表示这些总线都已扫描,后期不再需要扫描

 |   |       |

 |   |       |  // 查找互连器的irq_router对象,赋值到全局变量pirq_router,供后期使用

 |   |       |  //(互连器作为一种特殊的pci设备,除了要有pci_dev对象记录其设备信息,还要有irq_router对象记录其"编程接口" (互连器是可编程的))

 |   |       |- pirq_find_router()

 |   |       |   |

 |   |       |   |- struct irq_routing_table *rt = pirq_table  // "中断请求路径表"

 |   |       |   |- pirq_router = pirq_routers sizeof(pirq_routers) / sizeof(pirq_routers[0]) - 1  // pirq_routers[]:不同厂商的互连器信息表

 |   |       |   |

 |   |       |   |  // 在pci_devices全局链表中,找到互连器的pci_dev对象

 |   |       |   |  // irq_routing_table对象的rtr_bus(所在总线)和rtr_devfn(设备 功能号)成员,记录了互连器的位置 (互连器通常与pci-isa桥集成在同一芯片,作为pci设备连接在pci总线上)

 |   |       |   |- pirq_router_dev = pci_find_slot(rt->rtr_bus, rt->rtr_devfn)

 |   |       |   |   |- pci_for_each_dev(dev)

 |   |       |   |       |- if (dev->bus->number == bus && dev->devfn == devfn)

 |   |       |   |           |- return dev

 |   |       |   |

 |   |       |   |  // "中断请求路径表"和互连器的pci_dev对象,都包含互连器的生产厂商和设备id,优先以"中断请求路径表"提供的为准

 |   |       |   |  // 如果找不到对应的irq_router对象,表示互连器与中断控制器之间的连接,不需要软件设置 (比如全部为"硬连接")

 |   |       |   |- for(r=pirq_routers; r->vendor; r )

 |   |       |       |- if (r->vendor == rt->rtr_vendor && r->device == rt->rtr_device)

 |   |       |       |   |- pirq_router = r

 |   |       |       |   |- break

 |   |       |       |- if (r->vendor == pirq_router_dev->vendor && r->device == pirq_router_dev->device)

 |   |       |           |- pirq_router = r

 |   |       |

 |   |       |  // "中断请求路径表"还有一个exclusive_irqs字段,是一个位图,用于表示中断控制器上,应该避免共用的中断请求线 (arch/i386/kernel/pci-irq.c, 27行):

 |   |       |  /*

 |   |       |   * never use: 0, 1, 2 (timer, keyboard, and cascade)  (0号中断请求线,由时钟中断专用)

 |   |       |   * avoid using: 13, 14 and 15 (fp error and ide).

 |   |       |   * penalize: 3, 4, 6, 7, 12 (known isa uses: serial, floppy, parallel and mouse)

 |   |       |   */

 |   |       |- if (pirq_table->exclusive_irqs)

 |   |           |- for (i=0; i<16; i )

 |   |               |- if (!(pirq_table->exclusive_irqs & (1 << i)))

 |   |                   |- pirq_penalty[i] = 100  // 内核使用"惩罚量",对应需要避免的程度

 |   |

 |   |  // 穷举式扫描还未枚举的总线

 |   |- pcibios_fixup_peer_bridges()

 |   |   |- if (pcibios_last_bus <= 0 || pcibios_last_bus >= 0xff// 如果根据"中断请求路径表"补过漏了,不用再扫描

 |   |   |   |- return

 |   |   |- for (n=0; n <= pcibios_last_bus; n )

 |   |       |- for(dev.devfn=0; dev.devfn<256; dev.devfn = 8)

 |   |           |- if (!pci_read_config_word(&dev, pci_vendor_id, &l) && l != 0x0000 && l != 0xffff// 总线上有pci设备,才会被扫描

 |   |               |- pci_scan_bus(n, pci_root_ops, null)

 |   |

 |   |  // pirq_peer_trick()和pcibios_fixup_peer_bridges()属于顺带任务,pcibios_irq_init()之后的主要任务,是继续处理中断请求线的连接问题

 |   |- pcibios_fixup_irqs()

 |   |   |

 |   |   |  // 累积惩罚值 (有些dev->irq,在加电自检阶段,就已经被bios设置成非0值了,并由前面的pci_setup_device()读到了pci_dev对象中)

 |   |   |- pci_for_each_dev(dev)  // 遍历pci_devices全局链表上的pci_dev对象

 |   |   |   |- if (pirq_penalty[dev->irq] >= 100 && pirq_penalty[dev->irq] < 100000)

 |   |   |   |   |- pirq_penalty[dev->irq] = 0

 |   |   |   |- pirq_penalty[dev->irq]   // 有了pirq_penalty[]参考,内核就可以尽量保证,将剩余未接的请求线,接在中断控制器相对空闲的请求线上

 |   |   |

 |   |   |  // 扫描所有pci设备,对于已经连接到中断控制器的设备,获取其连接的请求线,记录到pci_interrupt_line寄存器

 |   |   |- pci_for_each_dev(dev)

 |   |       |- pci_read_config_byte(dev, pci_interrupt_pin, &pin)

 |   |       |- ...                       // #ifdef config_x86_io_apic (暂不关心)

 |   |       |- if (pin && !dev->irq)     // pci_interrupt_pin寄存器不为0(表示设备可以产生中断),但是dev->irq为0(表示还不知道设备连接中断控制器的哪根请求线)

 |   |           |

 |   |           |  // 根据"外设-pci插槽-路径互连器-中断控制器",逐层查找

 |   |           |  // 不过dev设备连接的那根互连器请求线,可能还未与中断控制器连接,第2个参数传0,正是表示只查找、不连接,让pcibios_lookup_irq()先别管未连接的情况

 |   |           |- pcibios_lookup_irq(dev, 0// 疑问1:对于i386 cpu,此时就已连接的情况,必定也由bios设置好了pci_interrupt_line,那么为0就表示还未连接,还有必要查找吗??

 |   |               |                          // 疑问2:pcibios_lookup_irq()找到连接的请求线后,为什么并没有补充记录到pci_interrupt_line寄存器??

 |   |               |

 |   |               |- struct irq_router *r = pirq_router

 |   |               |- pci_read_config_byte(dev, pci_interrupt_pin, &pin)

 |   |               |- pin = pin - 1                 // pin=1~4时,表示dev设备中断请求线,与pci插槽inta~intd连接,此处减1,用于后续作为数组下标

 |   |               |- info = pirq_get_info(dev)     // 获取dev设备所在插槽的irq_info对象 (用于描述pci插槽上的inta~intd请求线,与互连器的连接信息)

 |   |               |   |- struct irq_routing_table *rt = pirq_table

 |   |               |   |- int entries = (rt->size - sizeof(struct irq_routing_table)) / sizeof(struct irq_info)    // "中断请求路径表"中的irq_info对象个数

 |   |               |   |- for (info = rt->slots; entries--; info )

 |   |               |       |- if (info->bus == dev->bus->number && pci_slot(info->devfn) == pci_slot(dev->devfn))  // 匹配dev设备,与各个插槽的irq_info对象

 |   |               |           |- return info

 |   |               |- pirq = info->irq[pin].link    // link:pci插槽第pin根请求线,连接的互连器请求线号

 |   |               |                                // 4位:是否"硬连接"; 低4位(仅"硬连接"时有意义):连接的互连器请求线号

 |   |               |- mask = info->irq[pin].bitmap  // 与pci插槽第pin根请求线连接的互连器请求线,可以连接的中断控制器请求线位图

 |   |               |- mask &= pcibios_irq_mask      // 中断控制器0~2请求线,禁止共用

 |   |               |

 |   |               |  // 运行至此处,dev->irq一定为0,assign一定为0 (见pcibios_lookup_irq()函数的调用条件,和第二个参数值)

 |   |               |- newirq = dev->irq       // dev->irq为0,表示还不知道设备连接中断控制器的哪根请求线,一种可能是连接了,但还没查询,另一种可能是还没连接

 |   |               |- if (!newirq && assign)  // assign=1时(一定是后期,此时assign为0),如果dev->irq还是0,那就不得不进行连接了

 |   |               |   |- for (i = 0; i < 16; i )

 |   |               |       |- if (pirq_penalty[i] < pirq_penalty[newirq])

 |   |               |           |- request_irq(i, pcibios_test_irq_handler, sa_shirq, "pci-test", dev)

 |   |               |           |- newirq = i

 |   |               |

 |   |               |  // 当前场景,一定会执行到这里,并且进入以下第1或第2个分支,最终获取设备连接的中断控制器请求线 (assign=1时,newirq才能非0,才能进入第3个分支)

 |   |               |- if ((pirq & 0xf0) == 0xf0)

 |   |               |   |- irq = pirq & 0xf

 |   |               |- else if (r->get)

 |   |               |   |- irq = r->get(pirq_router_dev, dev, pirq)

 |   |               |- else if (newirq && r->set && (dev->class >> 8) != pci_class_display_vga)

 |   |               |   |- r->set(pirq_router_dev, dev, pirq, newirq)

 |   |               |

 |   |               |  // 连接到互连器同一根请求线的设备,一定也连接到中断控制器的同一根请求线 (pci_for_each_dev(dev)后续遍历到这些设备时,就不需要再调用pcibios_lookup_irq())

 |   |               |- pci_for_each_dev(dev2)

 |   |                   |- pci_read_config_byte(dev2, pci_interrupt_pin, &pin)

 |   |                   |- info = pirq_get_info(dev2)

 |   |                   |- if (info->irq[pin].link == pirq)

 |   |                       |- dev2->irq = irq

 |   |                       |- pirq_penalty[irq]

 |   |

 |   |  // "配置寄存器组"头部中保存的地址区间起始地址:

 |   |  //  出厂时,为设备内部的局部地址,对于i386 cpu,已在加电阶段,由bios设置为映射地址了

 |   |  //  内核为之建立resource对象,并加以验证和确认:过低的物理地址区间(被内核本身使用)、与其它区间冲突的地址区间,都要另行分配

 |   |

 |   |  /* resource结构:

 |   |      start和end表示区间范围,flags包含表示区间属性的标志位,parent,sibling和child用于按照位置关系,将所有resource对象组织在一起

 |   |

 |   |      struct resource ioport_resource = { "pci io", 0x0000, io_space_limit, ioresource_io };

 |   |      struct resource iomem_resource = { "pci mem", 0x00000000, 0xffffffff, ioresource_mem }; */

 |   |- pcibios_resource_survey()

 |       |

 |       |  // 为每条pci总线分配resource对象 (事后追认!!)

 |       |  // 每条pci总线占用的地址资源,实际就是所在pci桥的3个地址窗口:

 |       |  //  设备上,由所在pci桥的pci_io_base,pci_memory_base,pci_pref_memory_base寄存器记录

 |       |  //  内存中,由所在pci桥pci_dev对象的resource[7~9]记录

 |       |  // 实际上,pcibios_allocate_bus_resources()并不需要分配什么:

 |       |  //  总线地址早已在加电阶段由bios分配,并设置到pci_io_base,pci_memory_base,pci_pref_memory_base寄存器 (很多设置在加电自检阶段就已经完成了,pci_init()其实只是修复和补充)

 |       |  //  pci_bus对象的*resource[0~2],也已经在前面调用pci_read_bridge_bases()时,直接指向所在pci桥pci_dev对象的&resource[7~9],因此也不需要分配

 |       |- pcibios_allocate_bus_resources(&pci_root_buses)           // 建议仔细看一下函数定义上方的注释

 |       |   |- for (ln=bus_list->next; ln != bus_list; ln=ln->next// 被pcibios_resource_survey()调用时,bus_list=&pci_root_buses,递归调用时,bus_list=上层总线的children链表

 |       |       |

 |       |       |- bus = pci_bus_b(ln)     // list_entry()的封装

 |       |       |- if ((dev = bus->self))  // bus->self:总线自身所在的设备(pci桥) (bus->devices:连接在总线上的设备)

 |       |       |   |  // 仅为窗口分配resource对象 (pci桥本身具有的ram、rom区间,将在后面同普通pci设备一起,调用pcibios_allocate_resources()函数进行分配)

 |       |       |   |- for (idx = pci_bridge_resources; idx < pci_num_resources; idx )

 |       |       |       |

 |       |       |       |- r = &dev->resource[idx]

 |       |       |       |- if (!r->start)  // 当前区间没有对地址资源的需求 (比如子设备不需要io空间,则pci桥就不需要io窗口)

 |       |       |       |   |- continue

 |       |       |       |

 |       |       |       |  // "父节点"批发地址资源 (对于i386 cpu,"父节点"需要占用多少地址资源,已经由bios计算好了,内核只是"追加"resource对象)

 |       |       |       |- pr = pci_find_parent_resource(dev, r)   // dev:总线所在pci桥的pci_dev对象

 |       |       |       |   |                                      // r:区间原始信息 (在设备内的局部地址、大小等)

 |       |       |       |   |- for(i=0; i<4; i )

 |       |       |       |   |   |- struct resource *r = bus->resource[i]

 |       |       |       |   |   |- if (res->start && !(res->start >= r->start && res->end <= r->end))

 |       |       |       |   |   |   |- continue

 |       |       |       |   |   |- if ((res->flags ^ r->flags) & (ioresource_io | ioresource_mem))  // i/o区间,必须从i/o空间分配,存储器区间,必须从存储器区间分配

 |       |       |       |   |   |   |- continue

 |       |       |       |   |   |- if (!((res->flags ^ r->flags) & ioresource_prefetch))  //     分配区间(res)要求"可预取",但"父节点"区间(r)不支持"可预取"  ("可预取"区间,不能从不支持"可预取"的空间分配)

 |       |       |       |   |   |   |                                                     // 或:分配区间(res)不要求"可预取",但"父节点"区间(r)支持"可预取"  ("可预取"空间,尽量留给要求"可预取"的区间)

 |       |       |       |   |   |   |                                                     //    |-------------------|

 |       |       |       |   |   |   |                                                     // !((res->flags ^ r->flags) & ioresource_prefetch)

 |       |       |       |   |   |   |                                                     // |----------------------------------------------|

 |       |       |       |   |   |   |                                                     //  只要不是ioresource_prefetch,直接返回"父节点"的这块空间

 |       |       |       |   |   |   |- return r

 |       |       |       |   |   |- if ((res->flags & ioresource_prefetch) && !(r->flags & ioresource_prefetch))  // 不要求"可预取"的区间,迫不得已时也可以从"可预取"空间分配

 |       |       |       |   |   |   |- best = r

 |       |       |       |   |- return best

 |       |       |       |

 |       |       |       |  // 从批发的地址资源中,分配需要的地址区间

 |       |       |       |- if (!pr)

 |       |       |           |- request_resource(pr, r)  // 从pr上面,切割出一个r

 |       |       |               |- __request_resource(root, new)

 |       |       |                   |- if (end < start)          // 冲突:new->end < new->start

 |       |       |                   |   |-return root;

 |       |       |                   |- if (start < root->start)  // 冲突:new->start < root->start

 |       |       |                   |   |- return root;

 |       |       |                   |- if (end > root->end)      // 冲突:new->end > root->end

 |       |       |                   |   |- return root;

 |       |       |                   |- p = &root->child

 |       |       |                   |- for (;;)

 |       |       |                       |- tmp = *p

 |       |       |                       |- if (!tmp || tmp->start > end)  // 如果tmp是第一个child,只需要tmp->start > end即可

 |       |       |                       |   |                             // 否则上次循环,一定是从if (tmp->end < start)分支中,continue的,保证了上个child->end < start

 |       |       |                       |   |- new->sibling = tmp

 |       |       |                       |   |- *p = new

 |       |       |                       |   |- new->parent = root

 |       |       |                       |   |- return null         // 没有冲突区间,返回null

 |       |       |                       |- p = &tmp->sibling

 |       |       |                       |- if (tmp->end < start)

 |       |       |                       |   |- continue

 |       |       |                       |- return tmp              // tmp->start < end && tmp->end > start,说明new与tmp有重叠部分

 |       |       |

 |       |       |- pcibios_allocate_bus_resources(&bus->children)  // 递归

 |       |

 |       |  // 为pci设备分配resource对象 (第一趟,处理已经生效的地址区间)

 |       |- pcibios_allocate_resources(0)

 |       |   |- pci_for_each_dev(dev)  // 遍历所有pci设备

 |       |       |- pci_read_config_word(dev, pci_command, &command)

 |       |       |- for(idx = 0; idx < 6; idx // pci设备本身的ram区间 (最多6个)

 |       |       |   |- r = &dev->resource[idx]

 |       |       |   |- if (r->parent)  // parent已经指向"父节点",表示已经分配了resource

 |       |       |   |   |- continue

 |       |       |   |- if (!r->start)  // 区间起始地址为0,表示不需要分配地址资源的区间,或者暂时无法从"父节点"分配

 |       |       |   |   |- continue

 |       |       |   |  // 需要分配地址资源的区间

 |       |       |   |- if (r->flags & ioresource_io)

 |       |       |   |   |- disabled = !(command & pci_command_io)

 |       |       |   |- else

 |       |       |   |   |- disabled = !(command & pci_command_memory)

 |       |       |   |- if (pass == disabled)  // 第一趟,pass=0

 |       |       |       |- pr = pci_find_parent_resource(dev, r)    // "父节点"批发地址资源

 |       |       |       |- if (!pr || request_resource(pr, r) < 0// 从批发的地址资源中,分配需要的地址区间

 |       |       |           |  // 无法从"父节点"分配,先将该区间起始地址平移到0,后续由pcibios_assign_resources()统一变更到合适的位置

 |       |       |           |- r->end -= r->start

 |       |       |           |- r->start = 0

 |       |       |

 |       |       |- if (!pass// 仅第一趟执行,关闭rom区间:

 |       |           |          // rom区间一般只在初始化时由bios或具体的驱动程序使用,但地址资源还保留,如果需要,还可以在驱动程序中再打开

 |       |           |- r = &dev->resource[pci_rom_resource]

 |       |           |- if (r->flags & pci_rom_address_enable)

 |       |               |- r->flags &= ~pci_rom_address_enable

 |       |               |- pci_read_config_dword(dev, dev->rom_base_reg, ®)

 |       |               |- pci_write_config_dword(dev, dev->rom_base_reg, reg & ~pci_rom_address_enable)

 |       |

 |       |  // 为pci设备分配resource对象 (第二趟,基于第一趟剩余的地址区间,处理未生效地址区间)

 |       |- pcibios_allocate_resources(1)

 |       |   |- pci_for_each_dev(dev)

 |       |       |- ...

 |       |

 |       |  // 处理未能从"父节点"分配到地址资源(即起始地址为0)的区间 (真实意义上的"分配",而不是"追认")

 |       |- pcibios_assign_resources()

 |           |- pci_for_each_dev(dev)  // 遍历所有pci设备

 |               |- int class = dev->class >> 8  // "配置寄存器组"pci_class_device字段,高8位代表设备类型

 |               |- if (!class || class == pci_class_bridge_host)  // 设备无效(类型字段为0),或者是pci桥,不处理

 |               |   |- continue

 |               |

 |               |- for(idx=0; idx<6; idx // pci设备本身的ram区间 (最多6个)

 |               |   |- r = &dev->resource[idx]

 |               |   |- if ((class == pci_class_storage_ide && idx < 4) ||

 |               |   |   |  (class == pci_class_display_vga && (r->flags & ioresource_io)))  // ide存储设备(硬盘)的前4个区间,和vga设备的i/o区间是特例,这些区间既不需要分配,也不能改变

 |               |   |   |- continue

 |               |   |- if (!r->start && r->end)  // 起始地址为0,但结束地址不为0,表示需要分配地址资源的区间 (比如之前分配失败,经过"平移"的区间)

 |               |       |- pci_assign_resource(dev, idx)  // 分配总线地址,并设置"配置寄存器组"的相应字段

 |               |           |- const struct pci_bus *bus = dev->bus

 |               |           |- struct resource *res = dev->resource i

 |               |           |- size = res->end - res->start 1                                       // 区间大小

 |               |           |- min = (res->flags & ioresource_io) ? pcibios_min_io : pcibios_min_mem  // i/o地址区间位置不得低于4kb,内存地址区间位置不得低于256mb

 |               |           |

 |               |           |  // 为pci设备从所在总线分配地址资源 (ioresource_prefetch参数表示:如果设备区间"可预取",优先要求分配的地址区间也"可预取")

 |               |           |- if (pci_assign_bus_resource(bus, dev, res, size, min, ioresource_prefetch, i) < 0)

 |               |               |   |- for (i = 0 ; i < 4; i )

 |               |               |       |

 |               |               |       |- struct resource *r = bus->resource[i]     // r:所在总线的地址窗口

 |               |               |       |- if ((res->flags ^ r->flags) & type_mask)  // res:待分配的地址区间

 |               |               |       |   |- continue

 |               |               |       |- if ((r->flags & ioresource_prefetch) && !(res->flags & ioresource_prefetch))

 |               |               |       |   |- continue

 |               |               |       |

 |               |               |       |  // 分配总线地址

 |               |               |       |- allocate_resource(r, res, size, min, -1, size, pcibios_align_resource, dev)

 |               |               |       |   |                                              |

 |               |               |       |   |                                              |  // 避免使用bit8或bit9为非0的i/o地址:

 |               |               |       |   |                                              |  //     0000,0000b ~     1111,1111b:保留给母板使用

 |               |               |       |   |                                              |  //  01,0000,0000b 11,1111,1111b:有些外设只解析i/o地址低10

 |               |               |       |   |                                              |  // x01,0000,0000b ~ x11,1111,1111b:比如将001,0000,0000b~011,1111,1111b分配给外设a,将101,0000,0000b~111,1111,1111b分配给外设b

 |               |               |       |   |                                              |  //                                  并且外设a、b正好都只解析低10位,则实际上它们都分配到01,0000,0000b~11,1111,1111b地址区间,导致冲突

 |               |               |       |   |  // 寻找符合要求的区间                       |- if (res->flags & ioresource_io)

 |               |               |       |   |  // root:   总线上的地址窗口                     |- if (start & 0x300)

 |               |               |       |   |  // new:    待分配的地址区间                     |   |- start = (start 0x3ff) & ~0x3ff

 |               |               |       |   |  // size:   待分配区间的大小 (必为2的次幂)       |- res->start = start

 |               |               |       |   |  // align:  待分配区间地址地址对齐值 (=size)

 |               |               |       |   |  // alignf: 边界对齐函数指针 (pcibios_align_resource())

 |               |               |       |   |  // alignf_data: alignf()参数 (pci_dev对象)

 |               |               |       |   |- err = find_resource(root, new, size, min, max, align, alignf, alignf_data)

 |               |               |       |   |         |- for(;;)

 |               |               |       |   |             |- struct resource *this = root->child

 |               |               |       |   |             |- new->start = root->start

 |               |               |       |   |             |- if (this)                   // root有子节点

 |               |               |       |   |             |   |- new->end = this->start  // 扫描子区间

 |               |               |       |   |             |- else                        // root为叶子节点,没有子节点

 |               |               |       |   |             |   |- new->end = root->end    // root起点,作为待分配区间的起点

 |               |               |       |   |             |- if (new->start < min)

 |               |               |       |   |             |   |- new->start = min

 |               |               |       |   |             |- if (new->end > max)

 |               |               |       |   |             |   |- new->end = max

 |               |               |       |   |             |- new->start = (new->start align - 1) & ~(align - 1// 先按照align值对齐

 |               |               |       |   |             |- if (alignf)

 |               |               |       |   |             |   |- alignf(alignf_data, new, size)  // 再通过alignf()额外调整

 |               |               |       |   |             |- if (new->start < new->end && new->end - new->start 1 >= size)  // 找到大小符合要求的区间

 |               |               |       |   |             |   |- new->end = new->start size - 1

 |               |               |       |   |             |   |- return 0

 |               |               |       |   |             |- if (!this)

 |               |               |       |   |             |   |- break

 |               |               |       |   |             |- new->start = this->end 1

 |               |               |       |   |             |- this = this->sibling

 |               |               |       |   |

 |               |               |       |   |  // 将分配到的区间,添加到root->child队列

 |               |               |       |   |- __request_resource(root, new)

 |               |               |       |

 |               |               |       |  // 将分配地址设置到pci设备的"配置寄存器组"

 |               |               |       |- pcibios_update_resource(dev, r, res, resno)

 |               |               |           |- new = res->start | (res->flags & pci_region_flag_mask)

 |               |               |           |- if (resource < 6)

 |               |               |           |   |- reg = pci_base_address_0 4*resource  // pci_base_address_0~pci_base_address_5地址 (每个寄存器占4字节)

 |               |               |           |- else if (resource == pci_rom_resource)

 |               |               |           |   |- res->flags |= pci_rom_address_enable

 |               |               |           |   |- new |= pci_rom_address_enable

 |               |               |           |   |- reg = dev->rom_base_reg

 |               |               |           |- else

 |               |               |           |   |- return

 |               |               |           |- pci_write_config_dword(dev, reg, new)

 |               |               |           |- pci_read_config_dword(dev, reg, &check)

 |               |               |

 |               |               |  // 为pci设备从所在总线分配地址资源 (情非得已并且设备区间"可预取"的情况下,分配到的地址区间与其不符(即不"可预取"),也是可以的,相当于放弃使用设备的"可预取"特性)

 |               |               |- if (!(res->flags & ioresource_prefetch) || pci_assign_bus_resource(bus, dev, res, size, min, 0, i) < 0)

 |               |                   |- return -ebusy

 |               |

 |               |- if (pci_probe & pci_assign_roms)

 |                   |- r = &dev->resource[pci_rom_resource]

 |                   |- r->end -= r->start

 |                   |- r->start = 0

 |                   |- if (r->end)

 |                       |- pci_assign_resource(dev, pci_rom_resource)

 |

 // 出厂信息修正 (pci初始化结束阶段)

 |- pci_for_each_dev(dev)

     |- pci_fixup_device(pci_fixup_final, dev)

原文链接:https://bbs.kanxue.com/thread-278008.htm

网络摘文,本文作者:15h,如若转载,请注明出处:https://www.15cov.cn/2023/08/27/pci总线初始化过程(linux-2-4-0内核中的pci_init函数分析)/

发表评论

邮箱地址不会被公开。 必填项已用*标注

网站地图