Linux那些事儿 之 我是PCI(3)PCI的那些内核参数

经过上节的头脑风暴,咱们明白了,PCI这边儿入口虽然多,但还是有规律可循有法可依的,内核启动时,得一个一个严格的按照顺序调用它们来完成PCI子系统的初始化,不能乱了章法。这点儿并不是所有人都会明白的,比如前段儿时间厦门那出儿卖房事件里宣称“我海关有人,谁敢动我”的那位海关老婆,她就觉得有法是不如有人的。

(背景知识:厦门网11月14日电,06年11月5日,曾先生夫妻向被告购买了香秀里的这套房子,成交价为98万元。按照合同约定,房产交付时,被告应保证室内整洁,无结构性损坏,门窗及配套水、电、煤气等应当完好。2007年3月11日,被告按照合同约定的时间把房子交了出来。曾先生去验房时,发现这套房子“就像被洗劫过一样”:房子内所有的开关及插座面板都被拆了,厨房、卫生间的水龙头、水槽都不见了,家具只要是有门的、有抽屉的,全都被拆卸一空,连滑轮、拉锁等小零配件也都不放过,甚至床靠背的装饰也被挖掉。法院认为,被告未按合同约定按时、完整地交付房屋,已构成违约。制造这起闻所未闻事件的房东夫妇,前天被思明区法院判定得赔偿修复费7000余元、违约金2940元、买主5个月租房损失1.4万元。)
同时,咱们还明白,如果在grub文件kernel那一行添加有“pci=”这样的东东,在调用那些入口函数之前,就必须得先调用一个pci_setup函数来解析这部分内核参数。pci_setup函数在drivers/pci/pci.c里有定义
1403 static int __devinit pci_setup(char *str)
1404 {
1405          while (str) {
1406                  char *k = strchr(str, ',');
1407                  if (k)
1408                          *k++ = 0;
1409                  if (*str && (str = pcibios_setup(str)) && *str) {
1410                          if (!strcmp(str, "nomsi")) {
1411                                  pci_no_msi();
1412                          } else if (!strncmp(str, "cbiosize=", 9)) {
1413                                  pci_cardbus_io_size = memparse(str + 9, &str);
1414                          } else if (!strncmp(str, "cbmemsize=", 10)) {
1415                                  pci_cardbus_mem_size = memparse(str + 10, &str);
1416                          } else {
1417                                  printk(KERN_ERR "PCI: Unknown option `%s'/n",
1418                                                  str);
1419                          }
1420                  }
1421                  str = k;
1422          }
1423          return 0;
1424 }
1405行,友情提醒一下,参数里的这个str并不包括“pci=”,它只包含了等号后面的那一串选项,选项之间使用逗号做间隔,而且它们之间不能有空格。这个while循环的目的就是以逗号为地标,层层推进找出每个选项进行处理。还是举个例子吧,显得形象一点儿,假设内核参数里设置了“pci=nobios,nomsi,conf1”,那么这里的str就是“nobios,nomsi,conf1”,while循环要从str里面依次地解析出nobios,nomsi,conf1,并进行相应的处理,至于它们都什么意思,暂时不用去关心。你可以想像一下冰糖葫芦,都那么成串儿的排列着,不过不一样的是,冰糖葫芦你可以从上边儿那个开始吃,也可以从下边儿那个开始吃,没有法律去无聊的规定你吃的顺序,而这里的str却只能从第一个选项开始,一个一个流水线式的解析。
1406行,再假设这是第一次进入while循环,那么这个k就等于“,nomsi,conf1”。
1408行,有关字符串结束符,俺在讲USB Core的时候都已经强调的不能再强调了。这里要提醒你注意的是“++”这个自增运算符,你可千万别以为这行的意思是先把k向前移动一个字符的位置,然后将这个新位置设置为0,相反,它是先将k的位置,也就是“,nomsi,conf1”的第一个逗号那里设置为0,然后再将k移动一个字符,此时k就变成了“nomsi,conf1”,而str就变成了“nobios”。用C里的行话来解释的话,就是side effect和sequence point。
1409行,这个函数的精华就在if这儿,更准确的说,就在if的条件里边儿。先是判断str是不是为空,因为上面假设了这是第一次进入while循环,str现在还是“nobios”,显然不是为空的。如果str不为空,就会去调用pcibios_setup函数对str做处理,然后判断pcibios_setup(str)的返回值str是不是为空,看来不与pcibios_setup函数亲密接触一下,咱们是不可能知道这个返回的str究竟是个什么东西的。你可以在arch/i386/pci/common.c文件里找到pcibios_setup函数
346 char * __devinit pcibios_setup(char *str)
347 {
348          if (!strcmp(str, "off")) {
349                  pci_probe = 0;
350                  return NULL;
351          } else if (!strcmp(str, "bfsort")) {
352                  pci_bf_sort = pci_force_bf;
353                  return NULL;
354          } else if (!strcmp(str, "nobfsort")) {
355                  pci_bf_sort = pci_force_nobf;
356                  return NULL;
357          }
358 #ifdef CONFIG_PCI_BIOS
359          else if (!strcmp(str, "bios")) {
360                  pci_probe = PCI_PROBE_BIOS;
361                  return NULL;
362          } else if (!strcmp(str, "nobios")) {
363                  pci_probe &= ~PCI_PROBE_BIOS;
364                  return NULL;
365          } else if (!strcmp(str, "nosort")) {
366                  pci_probe |= PCI_NO_SORT;
367                  return NULL;
368          } else if (!strcmp(str, "biosirq")) {
369                  pci_probe |= PCI_BIOS_IRQ_SCAN;
370                  return NULL;
371          } else if (!strncmp(str, "pirqaddr=", 9)) {
372                  pirq_table_addr = simple_strtoul(str+9, NULL, 0);
373                  return NULL;
374          }
375 #endif
376 #ifdef CONFIG_PCI_DIRECT
377          else if (!strcmp(str, "conf1")) {
378                  pci_probe = PCI_PROBE_CONF1 | PCI_NO_CHECKS;
379                  return NULL;
380          }
381          else if (!strcmp(str, "conf2")) {
382                  pci_probe = PCI_PROBE_CONF2 | PCI_NO_CHECKS;
383                  return NULL;
384          }
385 #endif
386 #ifdef CONFIG_PCI_MMCONFIG
387          else if (!strcmp(str, "nommconf")) {
388                  pci_probe &= ~PCI_PROBE_MMCONF;
389                  return NULL;
390          }
391 #endif
392          else if (!strcmp(str, "noacpi")) {
393                  acpi_noirq_set();
394                  return NULL;
395          }
396          else if (!strcmp(str, "noearly")) {
397                  pci_probe |= PCI_PROBE_NOEARLY;
398                  return NULL;
399          }
400 #ifndef CONFIG_X86_VISWS
401          else if (!strcmp(str, "usepirqmask")) {
402                  pci_probe |= PCI_USE_PIRQ_MASK;
403                  return NULL;
404          } else if (!strncmp(str, "irqmask=", 8)) {
405                  pcibios_irq_mask = simple_strtol(str+8, NULL, 0);
406                  return NULL;
407          } else if (!strncmp(str, "lastbus=", 8)) {
408                  pcibios_last_bus = simple_strtol(str+8, NULL, 0);
409                  return NULL;
410          }
411 #endif
412          else if (!strcmp(str, "rom")) {
413                  pci_probe |= PCI_ASSIGN_ROMS;
414                  return NULL;
415          } else if (!strcmp(str, "assign-busses")) {
416                  pci_probe |= PCI_ASSIGN_ALL_BUSSES;
417                  return NULL;
418          } else if (!strcmp(str, "routeirq")) {
419                  pci_routeirq = 1;
420                  return NULL;
421          }
422          return str;
423 }
这个函数是个练家子,会使排山倒海这一招儿,或者说它是张惠妹的fans,喜欢她的《排山倒海》,这么一排排的if-else还真让人有点招架不住。其实有点C常识的人扫一眼就明白,这是在拿str逐个儿的和一些字符串做比较,而这些字符串应该不是第一次见了,就是前面贴出来的内核参数里“pci=”后边儿可能会有的那些选项。下边儿先挨个儿简单提一下,混个脸熟,日后还会遇到的。
348行,off选项意味着内核启动时就不会再有总线枚举这个过程了,也就是说你机子里挂在PCI总线上的那些设备就都游离于内核之外了,什么后果自己想想吧,所以使用起来要慎重,没事儿的时候最好不要设置它。前面从Documentation/kernel-parameters.txt找出来的内容已经说明了它只对X86 32位平台起作用,要知道pcibios_setup函数内核里不是只有一个的,这里是因为早就声明了基于32位X86平台,所以就从drivers/pci/pci.c里的pci_setup函数直接跳到了arch/i386/pci/common.c里的这个pcibios_setup,其实在arch目录里列出的那些个架构里,都有这么一个pcibios_setup,不过位置不同,所看到的风景也就不同,它们有些是什么都没做就直接返回,有些是没有处理off这一项,这点儿你了解就可以了。
如果指定了off,就会将变量pci_probe设置为0,pci_probe在arch/i386/pci/pci.h里声明并在common.c里定义
21                                 PCI_PROBE_MMCONF;
pci_probe的初值为几个宏的合作社形式,它们分别对应了PCI access mode的几种方式,在arch/i386/pci/pci.h里定义
15 #define PCI_PROBE_BIOS          0x0001
16 #define PCI_PROBE_CONF1         0x0002
17 #define PCI_PROBE_CONF2         0x0004
18 #define PCI_PROBE_MMCONF        0x0008
19 #define PCI_PROBE_MASK          0x000f
20 #define PCI_PROBE_NOEARLY       0x0010
21
22 #define PCI_NO_SORT             0x0100
23 #define PCI_BIOS_SORT           0x0200
24 #define PCI_NO_CHECKS           0x0400
25 #define PCI_USE_PIRQ_MASK       0x0800
26 #define PCI_ASSIGN_ROMS         0x1000
27 #define PCI_BIOS_IRQ_SCAN       0x2000
28 #define PCI_ASSIGN_ALL_BUSSES   0x4000
PCI_PROBE_BIOS对应了BIOS方式,PCI_PROBE_MMCONF对应了MMConfig方式,这好理解,看名字就知道了,不好理解的是PCI_PROBE_CONF1和PCI_PROBE_CONF2都对应了Direct方式,这是因为曾经有过两种PCI Configuration Mechanism,用行话来说就是Type1和Type2,内核要想不通过BIOS直接去访问设备的话,也必须得对应有两种访问方式,即这里的conf1和conf2。Type2主要是在PCI发展的少年时期,某些主桥用过,现在一般都不会再用了,但是为了兼容一些老的主板,conf2还是保留了下来。至于剩下那些宏碰到再说了。
off接下来是bfsort和nobfsort,本来在几个月之前,是没有这两个角色的,自然也就不会有351到357这几行,俺也就不会在这儿辛苦的码下边儿的这些字,不过既然偶然选择了新内核而不是几个月前的次新内核,那这两个东东是必然躲不过的了。人生本来就是由一系列的偶然所组成,之前的偶然决定了现在的必然,出生、上学、工作所有你做过的事遇到的人都是偶然,当然也包括俺说的linux的这些事儿,这些偶然组合起来就决定了俺接下来必然得说说bfsort和nobfsort的那些风花雪月。
话说几个月前吧,一个名叫Matt Domsch的小伙子发现了一件奇怪的事情,在DELL的一款有两个网口的服务器上,进入BIOS时,显示两个网口分别是NIC1和NIC2,而启动linux后,在2.4内核上显示的是eth0和eth1,在2.6内核上显示的调了个个儿,变成了eth1和eth0,接着他又在HP和SUN的一些服务器上也发现了类似的现象。经过了一番不屈不挠的论证分析,他发现了这个现象的根源,在2.4的内核里,是按照广度优先(breadth-first)算法来进行总线枚举的,得出的那个全局的所有PCI设备的链表中设备的顺序自然也是广度优先的。而2.6内核里,这个枚举过程变成了深度优先(depth-first)。具体在出现这个现象的服务器上,由于物理上的原因,NIC1出现在总线号更小的那个PCI总线上,但是使用深度优先算法时,NIC2比NIC1更早被发现,于是NIC2成了eth0,NIC1却成了eth1,如果换成了广度优先,NIC1就会更早被发现,eth0就不会给NIC2抢去了。显然正常情况下,是应该先找到NIC1后找到NIC2,NIC1对应eth0,NIC2对应eth1。
做人不能做到足协那地步,光知道叙述问题而回避怎么去解决,即使这个问题再小可它也总归是问题,咱们的温总理就说了:中国财富再多,除以13亿人,就少得可怜了;中国问题再小,乘以13亿人,也就很大了。所以说不要小看这个小问题,俺曾经就在某个地方遇到过eth0神秘失踪eth1鸠占鹊巢的情况,友邦惊诧了好大一会儿。这道理谁都懂,Matt Domsch小伙儿自然也懂,同时他也明白韩寒那句话:没有一个问题能在二十句话内解决,不论什么东西最后都要引到自己研究的领域中去,哪怕嫖娼之类的问题也是。于是他就为内核提交了一个有很多个20行长的patch,添加了bfsort和nobfsort,让用户去选择使用不使用广度优先,如果用户指定了bfsort,则枚举最后得到的PCI设备链表里就是按照广度优先的顺序排列。
至于怎么个广度优先和深度优先,讲到具体的总线枚举过程时再去深入了解,这里就不多说了,再说,关于算法俺是个菜鸟,不然就不会在北京西郊宾馆和清华园google大楼之间的那条路上来回徘徊了好多次之后还是被google无情的拒绝了。还是看看下面的pci_bf_sort,如果你指定了bfsort,它就会被设置为pci_force_bf,指定了nobfsort,就会设置为pci_force_nobf。pci_bf_sort、pci_force_bf、pci_force_nobf这几个生面孔同样也是Matt Domsch提交的那个patch里添加的,pci_bf_sort在arch/i386/pci/common.c的开头儿有定义,而pci_force_bf和pci_force_nobf出现在arch/i386/pci/pci.h里
34         pci_bf_sort_default,
35         pci_force_nobf,
36         pci_force_bf,
37         pci_dmi_bf,
38 };
358~375行的这些选项是针对BIOS访问方式的,如果你明确指定了bios,那说明你希望使用PCI BIOS而不是直接访问硬件,这表示内核,或者说是你完全信任BIOS,不过要永远记住,信任的代价可能是非常昂贵的,所以一般情况下咱们在PCI access mode那儿都是选择Any,让内核去决定,而不是在这儿明确的指明。接下来的nobios当然是相反的意思了。

nosoft的长相看,又是与排序相关的,前边儿讲Matt Domsch的那些风花雪月的时候有提到,在2.4的内核里,是按照广度优先(breadth-first)算法来进行总线枚举的,其实这种说法是不准确的,没有使用PCI BIOS时,确实是这样,但是使用了PCI BIOS时就不一定了,虽说通常情况下PCI BIOS也是按照广度优先这么一个顺序去枚举的,但是毕竟PCI BIOSSpec里并没有明确规定一个PCI BIOS应该遵守的顺序,这么一来就有了变数,而这个nosoft就是用来减少这个变数的。如果你指定了nosoft,就是告诉内核,即使使用了PCI BIOS的方式,也不按照PCI BIOS枚举的顺序去排序设备。

接下来的biosirq牵涉到PCI的中断机制。咱们已经知道了每个PCI总线上都可以挂上很多个功能设备,也知道了设备是可以主动向CPU发出中断请求表明自己需要的。同时,咱们也应该能够感同身受,就好像上头儿发往下边的体恤通常会被层层和谐掉一样,凡是下边发往上头儿的请求通常也是要经过那么几层来传达的,只是如果谁比较特殊,或者和上头儿关系好,会少几层麻烦而已(但中断控制器那一层总归是躲不开的),显然PCI设备并不属于这类特殊品种,和上头儿的关系也没那么铁,它们要是请求CPU干点啥事儿,得一级一级一层一层的向上转达。首先,他们得连接到PCI总线的中断请求线上,这东西每个PCI总线也就只有4个,对应了每个PCI插槽(slot)上的4个中断引脚(pinINTA#INTB#INTC#INTD,所以很多PCI设备得发扬开源共享的精神一起用。然后,PCI的这几条中断请求线并不是就可以直接接到中断控制器上了,之间还需要通过一个所谓的可编程中断路由器(PCI Interrupt Router),至于这个router是如何将PCI的各个中断请求线给接到中断控制器上的,又是分别接到中断控制器的哪个引脚上的,就依赖于具体的系统了。对于内核来说,这种中断的路由拓扑状况自然是必须得了然于胸的,怎么去做到了然于胸?可以通过PCI BIOS(如果板子上有BIOS并且支持的话),也可以自己搞定。那么这个biosirq就是告诉内核通过PCI BIOS去获取一个名叫中断路由表(PCI Interrupt routing table)的东东,从而达到对中断路由情况的了然于胸。不得不说的是,中断路由器只有在中断控制器为PIC的时候才用得着,如果为APIC,就不用麻烦他老人家了。

作为主板的至亲好友,BIOS自然是知道,也应该知道在PCI设备、PCI插槽、中断路由器等之间的中断线路是如何连接的,它也有义务和责任在自己的BIOS ROM里存储相关的一些信息,事实上PCI BIOS Spec里也是这么规定的,内核可以在0xF0000h~0xFFFFF这段儿地址上查找得到中断路由表。但是不幸的是,在05年的阳春三月,俺们正张罗着一顿又一顿的散伙饭的时候,一个小伙儿发现在自己的硬件环境里,BIOS不能在0xF0000h~0xFFFFF之间生成和保存中断路由表,于是他就提交了一个patch来解决这个问题,于是就有了pirqaddrpirqaddr允许你去告诉内核中断路由表保存在哪个地方,这样的话,内核可以直接检测这个地址来获得中断路由表,而不用再去在0xF0000h~0xFFFFF之间挖地三尺进行大范围的搜索。这么做还不经意间带来了一个好处,对于那些每次都使用一样的地址保存中断路由表的BIOS,即使他们老老实实确实使用的是0xF0000h~0xFFFFF之间的某个地址,你也可以使用pirqaddr明确的将它指定出来,从而减少内核在这个范围内查找时带来的消耗。

pirq_table_addr是在arch/i386/pci/common.c里定义的一个unsigned long数,simple_strtoul函数能够将字符串转换为unsigned long,在这里就是将pirqaddr所指定的地址转化为数字赋给pirq_table_addr

376~385行之间的这两个conf1和conf2对应的就是前面提到的两种PCI Configuration Mechanism。
386行,nommconf用来告诉内核不要使用MMConfig方式访问设备。

392行,noacpi,用来禁止使用ACPI处理任何PCI相关的内容,包括PCI总线的枚举和PCI设备中断路由。咱们已经习惯于将ACPI和电源管理牢牢的联系在一起,所以很难会理解它和PCI这边儿的关系。其实仔细瞅瞅ACPI的全称the Advanced Configuration & Power Interface,里边儿不仅有Power,还有Configuration,你就应该能够悟出点儿道了。ACPI出现的目的是在咱们的OS和硬件平台之间隔出个抽象层,这样OS和平台就可以各自发展各自的,新的OS可以控制老的平台,老的OS也可以控制新的平台而不需要额外的修改。ACPI这个抽象层里包含了很多寄存器和配置信息,绝大部分OS需要从BIOS得到的信息都可以从ACPI得到,并且趋势是未来的任何新的特性相关的信息都只能从ACPI得到,这些信息里当然也包括PCI设备的中断路由情况等。

acpi_noirq_setinclude/asm-i386/acpi.h里定义,如果编译内核的时候配置上了ACPI,它就将全局变量acpi_noirq设置为1,意思就是上边儿说的,否则,它就是一个空函数。

396行,在内核启动过程的开始阶段,会对PCI设备进行一次早期的扫描,这时会使用前面提到的type1方式尝试访问每个可能存在的PCI设备的配置空间,如果设备本身就不存在,那么尝试去访问它的配置空间的话,在一些有bug的板子上就会发生自检。为了避免这种情况,你可以使用noearly选项来禁止这个早期的扫描,当然,如果type1方式已经被禁止的话这次扫描自然也就不会发生。

400行,一看到CONFIG_X86_VISWS俺就高兴,说明有那么一大段不用去看了。

一路飘到412行,romPCI Spec规定,PCI设备可以携带一个扩展ROM,并将与自己有关的初始化代码放到它里边儿,内核通执行这些代码来完成与设备有关的初始化。同时,PCI Spec还规定了,这些代码不能在设备的ROM里执行,必须得拷贝到系统的RAM里再执行,于是ROMRAM这两个本来不搭嘎的东东就产生了联系,这个联系就是行话所谓的映射。这里的选项rom就是告诉内核将设备的ROM映射到系统的RAM里。

415行,assign-busses,表示内核将无视PCI BIOS分配的总线号,自己重新分配。

418行,routeirq,在ldd3里,Greg告诉我们,在驱动程序访问PCI设备的任何资源之前都要先调用pci_enable_device,这个函数会为设备完成中断路由,也就是分配中断请求线等工作。但是Greg语重心长的这句话并不是人人都能记住,于是为了防止某些PCI驱动没有调用pci_enable_device就去访问设备的资源,routeirq就粉墨登场了,它清楚明白的告诉内核不要信任那些写PCI驱动的,得自己为所有的PCI设备做中断路由。

到此算是呕心沥血的对pcibios_setup()里边儿排列的所有选项检阅了一遍,台下走的人不容易,台上检阅的人也不容易,所以要理解前年河南新密市的那些搞阅兵式的领导们,他们在凛冽寒风中喊出那么一句句“同志们好!”“同志们辛苦了!”也是很不容易的。
回到pci_setup函数1409行的那个if,pcibios_setup()的返回值已经很清楚了,不是NULL,就是将str原封不动的返回,如果返回值str为NULL,则说明这个选项已经在pcibios_setup()里面处理过了,这个if就不用再进去了,接着处理下一个吧,如果将str完好无损的返回了,就说明pcibios_setup()里陈列的那些选项里没有找到你指定的这个选项,需要再进到if里面碰碰运气。
扫一眼这个if里边儿的那些行,我们发现,仍然是一些if-else排列,不过比pcibios_setup()那里少多了,只涉及了三个选项,可以抱着一颗平常心去看一下。如果指定了nomsi,就会调用drivers/pci/msi.c里的pci_no_msi函数
671 void pci_no_msi(void)
672 {
673          pci_msi_enable = 0;
674 }
这个世界上像pci_no_msi()这么单纯的角色已经不多了,它只是将msi.c文件里的一个static变量设置为0,表示禁用MSI中断,如果你在menconfig的时候选上了“Message Signaled Interrupts (MSI and MSI-X)”,只要在内核启动的时候指定了nomsi同样也可以强行将它禁止掉。

cbiosizecbmemsize都是CardBus桥专用的,就此飘过。

在if里碰完了运气,接下来要将k赋给str,根据前面的假设,此时str为nobios,k为“nomsi,conf1”,将k赋给str之后就会开始下一次的while循环。当然nobios是肯定能够得到处理的,不过即使你哪天心情不好随便指定了一个字符串,从而找不到它对应的那个选项,内核也只会幽怨的打印一句“PCI: Unknown option …”,同时将它给忽略掉。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值