全部博文(44)
分类:
2010-03-08 11:40:23
smpboot_setup_io_apic()函数会调用setup_IO_APIC()完成所有的工作。setup_IO_APIC()流程如下:
1、 调用enable_IO_APIC()对初始化一些基本数据结构,这些结构是:
u irq_2_pin全局数组:用于记录IRQ和pin的对应关系,以IRQ为索引,可以得到对应pin的信息。Pin用结构体struct irq_pin_list表示,它有三个字段:
字段 |
描述 |
Apic |
该pin从属的IOAPIC,该值用于所以mp_ioapics数组 |
Pin |
管脚号 |
Next |
下一个pin结构体 |
表2-12 struct irq_pin_list结构体
目前Linux中irq_2_pin元素个数为2 * NR_IRQS。NR_IRQS是平台最大IRQ数量,对于IOAPIC,该值为224。对于PIC,该值为16。
u pirq_entries全局数组:若在内核启动参数中配置了PIRQ参数,其值记录在pirq_entries数组中。目前内核允许最多配置8个PIRQ。
u nr_ioapic_registers全局数组:用于记录每个IOAPIC的管脚数,目前内核最多支持64个IOAPIC,故该数组长度为64。
u ioapic_i8259全局变量:记录PIC所在的IOAPIC与管脚。有两个字段,apic表示IOAPIC(同struct irq_pin_list的apic字段),pin代表管脚号。
笔者:关于PIRQ内核参数的详细信息,请参考内核文档Documentation/i386/IO-apic.txt。呵呵,不要被名字迷惑以为内容很多,它除了讲PIRQ什么都没有。此外,如果看了该文档仍不明白PCI中断线和PIRQ的换算关系,可以参考PCI spec的2.2.6节,里面有一个INTA~D到PIRQ的换算公式。这里就不多做介绍了。PIRQ内核参数在ioapic_pirq_setup()函数中解析。
enable_IO_APIC()首先将irq_2_pin和pirq_entires初始化到无效状态,并读取每个IOAPIC的管脚数存入nr_ioapic_registers数组。接着探测是否有PIC接到IOAPIC上,这通过读取每个IOAPIC的PRT表,寻找是否有RTE被设置成了ExtINT模式实现。如果找到,把apic和pin信息记录在ioapic_i8259中。最后通过mp_irqs数组,验证MP table报告的PIC信息和自己读取到的是否相同,如果不同,信任MP table并更改ioapic_i8259相应字段。最后将所有IOAPIC的PRT表mask掉,RTE为SMI类型除外。
2、 设置一个bit map ——unsigned long io_apic_irqs。该bit map每一bit对应一个IRQ,该bit为1,表示该IRQ接IOAPIC,否则接PIC。如果系统处于ACPI模式,io_apic_irqs = ~0UL;否则,io_apic_irqs = ~(1<<2)。
笔者:如果系统遵循MP spec,IRQ2用于接第二片i8259,故系统中不会有IRQ2存在。对于ACPI模式,没找资料说PIC的接法。
3、 如果系统不处于ACPI模式,调用setup_ioapic_ids_from_mpc()。该函数根据从MP table得到的IOAPIC ID,设置各IOAPIC的APIC ID寄存器。并查找是否有冲突,如有冲突则重新分配,并更新mp_irqs中对应中断的IOAPIC ID字段。
笔者:还记得我们前面有一个关于APIC ID的题外话吗?我们说对于Pentium4和Xeon系列,IOAPIC ID只用于区分多个IOAPIC,即使LAPIC ID冲突也无所谓。在setup_ioapic_ids_from_mpc()中,如果系统的APIC是xAPIC,不用检查IOAPIC ID直接返回,因为在没有APIC BUS的情况下,它们毫无意义。什么是xAPIC?Pentium4和Xeon系列用的APIC就叫xAPIC。内核佐证了我们前面的说法。
4、 调用sync_Arb_IDs(),如果当前系统APIC使用APIC BUS通讯,该函数将广播一个Level触发、类型为INIT的IPI(Inter Processor Interrupt,处理器间中断),将各LAPIC的Arb同步为其自身LAPIC ID。
和LAPIC总线竞争
Arb,Arbitration Register,仲裁寄存器。该寄存器用4个bit表示0~15共16个优先级(15为最高优先级),用于确定LAPIC竞争APIC BUS的优先级。系统RESET后,各LAPIC的Arb被初始化为其LAPIC ID。总线竞争时,Arb值最大的LAPIC赢得总线,同时将自身的Arb清零,并将其它LAPIC的Arb加一。由此可见,Arb仲裁是一个轮询机制。Level触发的INIT IPI可以将各LAPIC的Arb同步回当前的LAPIC ID。
对于Pentium4和Xeon系列,总线竞争由前端总线协议决定。
笔者:IOAPIC是否有Arb用于总线竞争?我猜想是有的。但x86 spec没说,在看APIC系统实现时,关于总线竞争的部分又看的太模糊了,没大看懂。想着以后不用这东西,看着就没动力了,有兴趣的朋友可以深究一下。
关于IPI的内容,《Interrupt in Linux(软件篇)》中会介绍(如果我写了^_^)
5、 调用setup_IO_APIC_irqs()。该函数是IOAPIC初始化中的重中之重,几乎所有工作都是由它完成的,下面我们看看其流程:
a、 初始化各IOAPIC的PRT表。内核用struct IO_APIC_route_entry结构表示RTE,下表描述了内核初始化RTE的格式:
字段 |
对应RTE字段 |
初始值 |
Vector |
Vector |
见后面内容 |
Delivery_mode |
Delivery Mode |
Lowest Priority(001) |
Dest_mode |
Destination Mode |
Logical(1) |
Polarity |
Polarity |
irq_polarity()函数根据mp_irqs中的信息,或总线类型决定 |
Trigger |
Trigger Mode |
irq_trigger()函数根据mp_irqs中的信息,或总线类型决定 |
Mask |
Mask |
Enable(0) |
logical_dest |
Destination Field |
所有可用的CPU |
表2-13 内核初始RTE结构值
从表中可以看出,内核将RTE配置成了logical模式,并且中断消息的目的地是所有CPU,Delivery mode为lowest priority。这样IOAPIC的中断消息由优先级最低的CPU接收。
b、 调用pin_2_irq()将pin转换成IRQ。对于ISA中断,pin对应的IRQ由mp_irqs数组得到。对于PCI中断,使用GSI方式把pin转换成唯一的IRQ,其代码如下:
i = irq = 0;
while (i < apic)
irq += nr_ioapic_registers[i++];
irq += pin;
笔者:可以看出,内核使用了GSI的思想,同时我们也可以看出IRQ和GSI是个可以互换的概念。
c、 调用add_pin_to_irq()将IRQ和pin的对应关系存入irq_2_pin数组。
d、 如果是该IRQ接IOAPIC管脚(用前面提到的io_apic_irqs bit map判断),调用assign_irq_vector()为该IRQ分配一个vector,然后调用ioapic_register_intr()给该IRQ注册一个handler。分配的vector会存入一个irq_vector全局数组,用IRQ号为索引,可得到对应的vector。
的Vector分配策略
__ assign_irq_vector()写的比较晦涩,较为难懂。我算法烂,就不画流程图论述过程了。画了一副图,展示vector分配的策略:
图中有3个IOAPIC。根据Linux分配IRQ的策略,IOAPIC0 24个管脚对应IRQ0~23,pin0对应IRQ0。IOAPIC1的pin0对应IRQ24 …… 依此类推。由于vector本身是代表优先级的,为了公平,Linux将所有vector平均的分配给3个IOAPIC。IRQ0分配到vector49,IRQ24分配到vector50,IRQ48分配到vector51 …… 依次类推。Linux中设备可用的第一个vector是0x31,也就是vector49,最大可用vector为0xef。但实际上__ assign_irq_vector()的实现将最大可用vector限制到了238,最多支持8个IOAPIC(每个IOAPIC 24个管脚,且最后一个IOAPIC有3个管脚分配不到vector)。在分配的过程中,避开了vector 0x80。 这个函数看的我有点头疼,为什么限制到238?为什么从49开始分配?没用的vector留给谁的?暂时不去想了,大家可以深究一下。 笔者:上面例子中的假设,IOAPIC0的pin0对应IRQ0,实际上不是这样的。前面已经讲了ISA中断的IRQ从MP table得到,IRQ0实际对应pin2。这样假设只是为了论述方便。 ioapic_register_intr()注册的handler,只是通用的处理函数(如处理level中断的,处理edge中断的),它会具体再调用设备的中断处理函数。ULK3上的中断处理路径__do_IRQ()已经过时了,内核已引入generic IRQ layer。 如果我写了《软件篇》,会介绍该处理机制。内核文档Documentation/DocBook/genericirq.tmpl详细讲解了generic IRQ layer,很简单的,大家可以自己看看。 Vector是值越大优先级越高,PIT对应IRQ0,按Linux的分配策略,它的优先级最低。是我错了?还是PIT在Linux中本身就不重要? a、 对于ISA中断,调用disable_8259A_irq()将PIC上对应的管脚mask掉(进入APIC模式后,PIC要被mask掉)。 b、 至此,我们已经配置好一个RTE的全部信息,调用__ioapic_write_entry()将该RTE写到IOAPIC中。 1、 调用init_IO_APIC_traps()为irq_vector数组中未分配到vector的IRQ设置默认的处理函数(通常这些IRQ不会发生,但Linux尽可能的保证安全。这个属于generic IRQ layer的内容,就不多讲了)。 好了,搞定。IOAPIC已经设置好了,有些内容我们没提到,例如check_timer(),有机会在说吧。