首页
社区
课程
招聘
PCI总线初始化过程(linux-2.4.0内核中的pci_init()函数分析)
2023-7-13 19:07 15265

PCI总线初始化过程(linux-2.4.0内核中的pci_init()函数分析)

2023-7-13 19:07
15265

1. PCI初始化的主要目标

  PCI设备(包括PCI桥),与主板之间的连接关系为:设备-[(PCI次层总线)-(PCI-PCI桥)]*-{(PCI主总线)-(宿主-PCI桥)}-主板(*表示0或多层)。总之,所有PCI设备,都直接或间接的连接到了主板上,并且每个PCI设备的内部,必然存在一些存储单元,服务于设备功能,包括寄存器、RAM,甚至ROM,本文将它们称为”功能存储区间”,以便与”配置寄存器组”(用于设备配置的存储区间)区分。

  • 为”功能存储区间”,分配地址
    为了避免存储单元相互冲突,使CPU可以正常访问所有PCI设备的所有”功能存储区间”,PCI初始化阶段,必须将这些区间,映射到独立的内存或I/O地址区间*(存储单元可以用内存访问指令访问,还是用I/O指令访问,就是由映射到什么区间决定,而跟存储单元是什么介质,以及所在的硬件无关)*。
  • 为PCI桥,分配地址窗口
    PCI桥作为一种特殊的PCI设备,除了服务于自身的”功能存储区间”,还要为次层总线占据一份内存或I/O区间(总线上没有寄存器,占用的地址空间由所在PCI桥记录),以供次层总线上的PCI设备分配,并且用于判断一个地址,是否在次层总线内部,对来自上游或下游的访问地址进行过滤。
  • 将可以产生中断的设备,连接到中断控制器
    “PCI设备-PCI插槽-中断请求路径互连器”之间,中断请求线的连接都是由硬件设计决定,只有”中断请求路径互连器-中断控制器”之间的连接,有些情况需要软件设置,一旦所有元件之间的连接都确定了,PCI设备连接着中断控制器的哪根线,就也确定了,并且由内核负责,将其记录到PCI设备的PCI_INTERRUPT_LINE配置寄存器*(注意,这个寄存器只起记录作用,修改其值,并不会改变中断请求线的连接关系)*。
  • 管理对象分配
    分别为所有PCI总线和设备,分配pci_bus和pci_dev管理对象,记录设备信息(比如,将映射的内存和I/O区间,记录到resource成员,将连接的中断控制器请求线,记录到irq成员)。

2. “配置寄存器组”头部


  “配置寄存器组”,一方面提供设备的出厂信息,另一方面,用于系统软件对设备进行配置。其中,前64字节,必须按照PCI标准使用,称为”配置寄存器组”的头部,头部又分为”0型”、”1型”和”2型”,不过,不管哪种头部,前16字节的格式都是一样的(头部类型就包含在其中),只是后48字节格式不同。头部之后的192字节,由具体设备自行使用,如果不需要,没有也是可以的。

2.1. type0 header

  • PCI_HEADER_TYPE(见:drivers/pci/pci.c,pci_setup_device()函数)
    头部类型:0,普通PCI设备(包括非桥设备,以及除”PCI-PCI”和”PCI-CardBus”之外的桥设备(比如:”宿主-PCI”桥、”PCI-ISA”桥等));1,PCI-PCI桥;2,PCI-CardBus桥(专用于笔记本)。
  • PCI_CLASS_DEVICE(见:include/linux/pci_ids.h, 1~118行)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
8
 |- 0x02:网络设备
 |  |- 8== 0 && PCI_CLASS_PROG == 0
 |     |- Ethernet网卡
 |- 0x07:简单通信控制器
 |  |- 8== 0x01:并行口
 |     |- PCI_CLASS_PROG
 |        |- 0:单向
 |        |- 1:双向
 |        |- 2:符合ECP 1.0规定
 |- 0x06:PCI桥
    |- 8
        |- 0x00"宿主-PCI"
        |- 0x01"PCI-ISA"
        |- 0x04"PCI-PCI"
        |- 0x07"PCI-CARDBUS"
  • PCI_VENDOR_ID
    制造厂商代号。
  • PCI_DEVICE_ID
    设备代号(用于区分同一家厂商的不同产品)。
  • PCI_BASE_ADDRESS_0~PCI_BASE_ADDRESS_5
    设备中除了配置寄存器,还包含另外一些寄存器,用于实现设备功能,为了表述方便,可以将其称为”功能寄存器”,PCI_BASE_ADDRESS_0~PCI_BASE_ADDRESS_5作为配置寄存器,就是用于配置前提供设备中各个”功能寄存器”区间的大小,配置后为这些区间设置合适的PCI地址。另外,直接读取PCI_BASE_ADDRESS_0~PCI_BASE_ADDRESS_5,得到的是区间地址加上一些标志位,先往里面写入全1再读,就是区间长度。
  • PCI_ROM_ADDRESS
    设备中除了包含”功能寄存器”,还有可能包含一块ROM,PCI_ROM_ADDRESS配置寄存器,就是用于提供ROM大小,以及配置ROM的PCI地址。

2.2. type1 header

  • PCI_PRIMARY_BUS
    PCI桥的上游总线号。
  • PCI_SECONDARY_BUS
    PCI桥的下游总线号。
  • PCI_SUBORDINATE_BUS
    以PCI桥下游总线为根的总线树上,最大的总线号(为总线编号时,是按”深度优先”的方式进行遍历,所以根的编号不是最大),CPU可以以此判断,要访问的PCI地址,是否落在当前PCI桥连接的总线树上,避免遍历所有总线。

2.3. IO空间”0xCF8~0xCFF”

  根据PCI规范说明,可以通过”0xCF8”和”0xCFC”两个32位寄存器,读写PCI设备的”配置寄存器组”(对于内核开发,直接按照规范使用即可,不需要关心PCI规范的硬件实现,比如为什么是”0xCF8”和”0xCFC”这两个I/O端口号,以及根据其中内容寻找并读写PCI设备的具体过程)

  地址寄存器:用于指定配置寄存器地址;
  数据寄存器:对于读操作,用于从中获取读取结果,对于写操作,用于往里填充写入内容。
  由于目标地址包含总线号和设备号,所以读写前,所有PCI总线和设备,都要有自己的编号。其中,对于总线编号, PCI桥提供了PCI_PRIMARY_BUS和PCI_SECONDARY_BUS配置寄存器,由软件进行设置,而设备编号,却没有对应的配置寄存器。个人理解,这是因为,总线编号,受总线在树中的位置决定,是PCI厂商无法预测的,而设备编号,只要能够表明设备在单条总线上的局部位置即可,所以可以与PCI插槽位置等效,这在PCI总线出厂时就能确定,所以不需要软件执行枚举编号。
  至此,还可以看出PCI设备中,配置寄存器与”功能存储区间”读写渠道的区别:通过”0xCF8”和”0xCFC”寄存器读写”配置寄存器”->通过”配置寄存器”,配置”功能存储区间”地址->直接根据配置地址访问”功能存储区间”。

3. PCI BIOS

  BIOS程序烧刻在ROM(只读/不挥发内存),其中,有些厂商的主板,BIOS提供了PCI操作功能,就称为”PCI BIOS”。对于CPU来说,主板加电后,BIOS程序立即就可以执行,而内核程序,必须先由BIOS从MBR(主引导扇区),加载引导程序,再由引导程序加载到RAM(内存),才可以执行。

3.1. 加电自检阶段

  “PCI BIOS”包含的很多PCI操作功能,在加电自检阶段就会被执行,本意是为内核省事情。但是,由于一些厂商的BIOS,存在这样或那样的问题,以及嵌入式主板中,甚至没有BIOS,所以Linux内核自己实现了一套独立的PCI操作接口,可以完全不依赖BIOS,不过对于已经在自检阶段完成的操作,Linux内核会尽量保持BIOS的设置,有些情况,会调用自己实现的接口,对其进行补充或修正,有些情况,甚至不再调用自己实现的接口,完全接受BIOS的处理结果。

3.2. 内核执行阶段

  “PCI BIOS”还有一些PCI操作功能,作为接口提供给内核程序使用,当内核希望自己完成一些PCI操作,又不想过分关注PCI硬件spec时,就可以通过lcall指令,直接调用“PCI BIOS”提供的功能。不过,同样为了避免BIOS中的各种问题,以及没有BIOS的情况,Linux内核对这些接口,也自己实现了一份,并且,Linux内核中的PCI操作函数,分别通过CONFIG_PCI_BIOS和CONFIG_PCI_DIRECT宏,选择调用哪套接口。显然,这两个宏至少要有一个是打开的,并且,两者互不相斥,都打开时,内核通常优先使用自己的接口,或用自己的接口,对BIOS接口的操作结果进行修正。

3.3. 内核对BIOS的依赖

  《Linux内核源代码情景分析》,p1025:

  《Linux内核源代码情景分析》,p1030:

  对于书上的说法,我一开始误解为:不管什么情况,即使没有BIOS,内核也能自己完成PCI操作,而是否依赖,可以通过CONFIG_PCI_BIOS宏,进行控制。
  带着这个误解,分析完i386架构下的pci_init()函数后,我产生了一个困惑:没有看到设置PCI设备PCI_IO_BASE和PCI_MEMORY_BASE寄存器的代码。然而,为各个PCI设备配置内存和I/O空间,可是PCI初始化的根本目标之一,所以既然内核没有设置,那就一定依赖了BIOS的设置,这就跟我的误解冲突了。
  为了重新理解书上的说法,我首先想到的是,不定义CONFIG_PCI_BIOS宏,只会影响内核代码的编译结果,关闭内核函数对BIOS接口的调用,并不能影响加电自检时BIOS执行哪些PCI操作,因为BIOS程序在主板出厂时,就已经固定了。
  i386架构下,内核代码所有对BIOS接口的调用,都在pcibios_init()或它调用的函数中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
pcibios_init()
 |- pci_find_bios()                          // #ifdef CONFIG_PCI_BIOS
 |   |- check_pcibios()
 |   |   |- lcall &pci_indirect              // 通过call指令,进入BIOS调用入口
 |   |- return &pci_bios_access
 |       |- pci_bios_OP_config_SIZE()        // OP: read/write; SISE: byte/word/dword
 |           |- lcall &pci_indirect
 |- pcibios_irq_init()
 |   |- pcibios_get_irq_routing_table()      // #ifdef CONFIG_PCI_BIOS
 |   |   |- lcall &pci_indirect
 |   |- pirq_find_router()
 |       |- pirq_router = &pirq_bios_router  // #ifdef CONFIG_PCI_BIOS
 |           |- pcibios_set_irq_routing()
 |               |- lcall &pci_indirect
 |- pcibios_sort()                           // #ifdef CONFIG_PCI_BIOS
     |- pci_bios_find_device()
     |- lcall &pci_indirect

  另外,为防止看漏了,我对内核设置PCI_MEMORY_BASE寄存器的语句,进行了搜索:

  结果发现,i386架构下,内核代码确实没有设置过PCI_IO_BASE和PCI_MEMORY_BASE寄存器,但在arm架构下(arch/arm/kernel/bios32.c),是由内核自己配置:

1
2
3
4
5
6
pcibios_init()
 |- pci_assign_unassigned_resources()
     |- pbus_assign_resources()
         |- pci_setup_bridge()
             |- pci_write_config_dword(bridge, PCI_IO_BASE, l)
             |- pci_write_config_dword(bridge, PCI_MEMORY_BASE, l)

  所以,结合以上两点可知,判断pcibios_init()是否依赖BIOS,还要看它是否依赖BIOS在加电自检阶段的操作结果,并且i386架构下的pcibios_init()函数,确实是依赖的。
  那就是说,虽然Linux内核独立实现了一套PCI操作接口,使得Linux内核可以完全不依赖BIOS,实现任何PCI操作,不过,这只是为最终的PCI操作是否完全不依赖BIOS,提供了选择,比如arm CPU常用于嵌入式系统,通常根本没有BIOS,所以arm架构下实现PCI操作,必须选择完全使用内核接口,而对于i386 CPU,可以确定加电自检阶段,有些操作必然已由BIOS完成,并且完成的没问题时,就可以选择仍然”依赖”BIOS(可以依赖时,不依赖白不依赖)。
  搞清楚这一点很重要,要不然分析代码时,就可能会期待在pci_init()函数中,看到根据所有PCI设备汇总信息,分配PCI地址的过程,却看不到,有些配置也会让人觉得”来历不明”,还没见到设置,内核就认为已经设置了。

4. pci_init()函数分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
// 声明:以下分析的代码,是针对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对象,用于记录总线信息
 |   |   |- 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()  // #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对象组织在一起
 |   |
 |   |     总线地址空间:(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)


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2023-7-24 17:35 被jmpcall编辑 ,原因: 大图上传后看不清了,替换为文字上传
上传的附件:
收藏
点赞8
打赏
分享
最新回复 (3)
雪    币: 10
活跃值: (379)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
nikoladi 2023-7-15 17:34
2
0
雪    币: 3350
活跃值: (3377)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
fengyunabc 1 2023-7-15 17:59
3
0
感谢分享!
雪    币: 19349
活跃值: (28971)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
秋狝 2023-7-15 23:05
4
1
感谢分享
游客
登录 | 注册 方可回帖
返回