PCI总线初始化过程(linux-2.4.0内核中的pci_init()函数分析)
2023-7-13 19:52:33 Author: bbs.pediy.com(查看原文) 阅读量:4 收藏

// 声明:以下分析的代码,是针对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操作接口 (

 |   |- 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操作接口 (

 |   |- 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对象,用于记录总线信息

 |   |   |- struct pci_bus *b = pci_alloc_primary_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<howmany; 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()  //

 |   |   |

 |   |   |  // 根据"中断请求路径表"信息,查询"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)

 |   |       |- ...                       //

 |   |       |- 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对象组织在一起

 |   |

 |   |     总线地址空间:(pci_init()之前,已经被"批发"走了一部分)

 |   |      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, &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.pediy.com/thread-278008.htm
如有侵权请联系:admin#unsafe.sh