北桥.PCI.linuxPCI中断处理

2977阅读 0评论2012-07-28 walterpeng
分类:

2008.8.9  , rev 2009.4.13

最开始只是想搞明白cpu 的load save指定的地址是如何正确的分别送到PCI和内存控制器的.....
*感性认识 Dell 630
*PCI  和北桥
*P35 芯片组
*PCI 和DMI, PCI 地址/内存地址 decode 简单原理
*MCH-P35 内存影射配置
*P35 北桥对pci 配置 cycle的处理流程
*
ICH9  PCI中断配置
*
恶补APIC
*恶补linux PCI中断处理



Dell 630

手头只有这个机器,就从这个机器为例吧, 但是除了这个章节以后的都采用P35芯片组为例, 因为P35手册说的更详细.


lshw -X (需要安装lshw-gtk), 类似windows下的设备管理按照链接排序的输出, 比较能够反映系统设备的链接情况. Dell630 采用了独立的NV显卡,显然采用了PM965芯片组.




这个图仅做为一个参考,能获取一个大致的印象.

 

PCI bus 和北桥


从上面linux内核暴露的数据看,几乎所有的设备都出现在PCI总线上. 从intel的芯片手册看来, 确实是这样的. 下面参考P35的芯片手册,大概学习一下.

MCH的简介可以参考:    MCH 负责 CPU, RAM, AGP, PCI (PCIE),  southbridge 之间的通讯. 带有图形芯片的北桥又叫做GMCH. 系统组成图中,MCH总是画在上方, 上北下南么,北桥的叫法由此而来.

下面这个图是从PCI2.2 规范中摘出的一个系统组成图:
北桥,就是MCH(memory controller hub), 由一个memory controler 和一个host-2-PCI的bridge组成. 在intel x86芯片体系下,内存存控制器从配置的角度看也是接到PCI总线的。


P35 芯片组


我的机器是p35芯片组的,可以在这里找到芯片手册:







这里首先搞清楚北桥芯片内的几个主要设备:
  • Device 0: 内存控制器和Host Bridge. MCH物理上链接CPU, 在MCH内部,所有设备看起来都是链接(或通过)到PCI bus 0的.从配置的角度可以说CPU直接链接PCI host Bridge, host bridge之后是PCI bus 0.  这个个设备包含了标准pci头信息,PCI Express基址寄存器,, DRAM 控制寄存器(包括thermal/throttling), DMI配置寄存器, 以及其他 (G)MCH 特定的寄存器.
  • Device 1: Host-PCI Express Bridge. 逻辑上PCI express也作为一个“virtual” PCI-to-PCI bridge 出现在PCI bus #0 上.是标准的PCI-PCI桥设备, 并且包含 PCI Express/PCI 的配置寄存器..
  • Device 2: 内部集成的显卡控制器.逻辑上出现在PCI bus0, 物理上包含2D, 3D的控制寄存器.
  • Device 3: Manageability Engine Device. ME Control.

感觉如下理解这些关系比较好:
    从配置上是CPU ->host bridge --> PCI bus 0 (hostbridge/memcontroler, pci express, internal grapics, ME).  物理上,这些设备都在MCH(北桥)内,共同提供出host interface, memory interface,  pci express interface,  Control link/DMI接口(到ICH9) (其他接口略).
    在cpu进行内存或着IO访问的时候, 北桥负责正确的decode不同地址范围到不同设备. 这些地址范围大多可以配置, 配置这些decode寄存器的方式就是通PCI bus0上的,上面提到的各个设备来进行.
   纯属猜测: 北桥在decode这些地址的时候直接用这些定义地址的寄存器, 不会有什么属于哪个设备的顾虑, 他们毕竟都在北桥内,只是export 的配置方式是通过PCI而已.



P35 -PCI 和DMI关系

MCH 到南桥ICH (i/o controller hub)的通讯是通过intel自己的DMI总线(direct media interface)进行的.先来看看PCI地址空间内的详细分布:

PCI 这段地址空间内有一部分是交给 DMI 来 decode 的,另外一部分是给PCIE的配置空间用的,最后还有APIC/BIOS/FSB Interrupt占用了一小部分,  这个DMI从物理上链接P35和ICH9, P35 +ICH9 芯片组中, DIM和PCI可以说密不可分, P35 MCH中有4个设备, 剩余的设备全在ICH9中, ICH9的大部分设备也是挂在PCI bus 0上的的,包括usb这些外部总线.

上图中显示 DMI interface的decode 方式是subtractive decode. 这个的含义对于理解MCH的工作很有意义..

  • Subtractive decode: 没有被其他设备positively 声明为属于别人的地址,4 个cycle的延迟之后,将被这个设备decode,

  • Positive decode:  只转发/decode 自己声明的部分.

  • Negative decode: 转发/decode 不属于自己地址范围的部分, 考虑设备从secondary 侧访问primary 侧的情形.


恩, 其实不复杂, 在MCH中的设备总是positive decode, 包括对内存的访问, 然后如果MCH没有响应, 4 cycle后, 送DMI处理, DMI经过自己的信号转换,到ICH9中再还原这个地址访问, 看看有没有合式的的设备decode这个地址. PCI和DMI的关系这是很重要的一条.





P35 MCH memory map

MCH 的一个重要功能就是提供到host cpu 的物理连接, 从cycle角度看, MCH把CPU发起的I/O cycles decoded 到 PCI Express, DMI, 或者 (G)MCH 的配置空间. CPU 发起的memory cycles 被 decoded 到 PCI Express, DMI, or 系统内存.

另外MCH提供了bus snooping的功能:从PCIE设备发起的,以及从DMI发起的到系统SDRAM的内存访问在host bus上被snoop(PCI Express 设备访问noncacheable system memory是不需要被 snoop 的)

给出Memory map的多是SOC系统,而PC上的memory map需要看芯片组手册才有相关的描述. Intel的P35-MCH 文档中给出一副描述 Memory Map 的图,如下:


有了上面的分析, 对这个图还算有点感觉. 关键的几个配置寄存器: TOLUD, TOUUD都是在device 0这个设备中的.


然后看看北桥对pci 配置 cycle的处理流程, 应该很清楚了.

注:PCI Express 的bus编号是纳入pci系统的需要统一编号.MCG D1中的寄存器 Sec bus#/sub bus# 分别是pci 分给这个pci -pci express 桥的子bus编号和子bus中最大的编号..



ICH9  PCI中断配置

初看之下发现ICH9(其实从ICH2开始就有)提供了8条PCI 中断请求线,  感到非常诧异, pci 不是只有INTA-D 4个吗. 难道PCI标准不是这个意思.

后仔细考量发现PCI标准说的是设备有4个pin 可以链接中断线, 并且给了个implment notes(Revision 2.3 p.14 ), 来建议如何链接这个4个pin到系统中断控制器. 关键是,这个例子的前提是假设有4个未用的IRQ(系统中断控制器引脚), 得来的. 

虽不太清楚ICH的多出来的PIRQ[E..H]到底何方神圣, 起码PCI规范没有定死PCI设备的4个pin如何接到中断控制器. 仔细查看ICH9的芯片手册, 其内部设备没有使用PIRQ[E..H], 关于这几根中断线, 手册说明如下:
1. 首先如果不用这几根中断线的话, 可以用做GPIO
2. 在非APIC模式, 可以路由到IRQ 3, 4, 5, 6, 7, 9, 10, 11, 12, 14 or 15 , 每个都有单独的路由寄存器. (也包括PIRQ[A..D])
3. 在APIC模式下, 这些引脚直接链接到IOAPIC: PIRQ[E-H]#-> IRQ[20-23].


ICH9包含的寄存器对PCI中断的描述很详尽:
1) 每个设备都有一个寄存器描述其4个中断PIN, INTA..D#链接到那条中断引线:PIRQ[A-H]#, 比如Device 25 Interrupt Route Register [ICH9, P371], 一个16bit的寄存器, 描述这个信息.
2) 最后还有一个PIR[pci interrupt router],来控制pci中断引线接入的IRQ,  先是ICH9, P144页对Steering PCI Interrupts的描述:
ICH9 有PIRQx Route Control registers, 位于Device 31:Function 0 的offset 60–63h and 68–6Bh , 可以将PIRQA#-PIRQH# 路由到IRQ  3–7, 9–12, 14 or 15.  如不需要可以禁止.  当将PIRQx#路由到一个IRQ后,需要将其ELCR设置为level 触发. PIRQx#是active low的, ICH9 将其翻转后接到PIC, 如此就不能和SERIRQ 的active high 设备共享同一中断, active low 可以. [RFC]  而寄存器描述好难找, 在p426, p428, 名字叫做PIRQ[n]_ROUT—PIRQ, Routing Control Register. 不复杂就不列到这里了.

在APIC模式下,就比较简单了, 如果配合MSI就更为简单.


恶补APIC

linux 关于APIC有许多名词,上面说到了INTx#,  PIRQx#, IRQx#, APIC又引入了GSI, global system interrupt. GSI对应于PIC的IRQ. 千万别忘记GSI/IRQ到cpu的verctor也是需要一个简单的影射的. 下图就是GSI vs IRQ.

在透过一个bridge的时候, INTx#对应的新的pin是通过下面的方法换算的,这个东西称作是PCI-PCI Bridge Swizzle. 典型的x86 上的bridge换算方式如下,这个应该是PCI规范impliment notes 提到的方式:
new_pin = (child_slot + child_pin) % 4


LVT
LVT 是local APIC 自己的中断源配置表, LAPCI自己能产生 APIC timerer, 热量探测,性能计数溢出, APIC错误等中断. 通过LVT可以为其分配vector.


IOAPIC PRT RTE
IOAPIC 有PRT配置表, 其表项就是RTE. 其中可以配置对应中断送达的CPU, 极性, 触发方式, 屏蔽, 还有我们最关心的vector.


LAPIC
在MP系统, 初始化后LAPIC 被分配唯一一个ID,可以通过寄存器读取. 这个ID也是IOAPIC中断送达目标CPU的标识. LAPIC可以不连续,但是必须唯一.
LAPIC中也有类似PIC的 :IRR 收到单未提交cpu,  ISR:送达CPU但未完成,  TMR: 这个不一样, 是处理中的中断之触发模式. 


具体的APIC细节繁复,需要看专门的著作了.对于linux, 也恶补一番.


恶补Linux PCI 中断

*PIC 时代 $PIR表的获取方式, 非APIC模式有效, 主流计算机上已经过时了...
x86 32bit有个配置选项, biosirq, 意为通过BIOS的PIR表(pci 中断路由表) ,来探测PCI中断. 可以搜搜看pcibios_get_irq_routing_table, 是PCI BIOS提供的获取$PIR地质的函数.

pcibios_irq_init 里边写的很明白, 如果是用apic模式就把PIR表指针置为空, 作废掉这个表.



* APIC模式下中断路由的主要数据结构
先说探测完成后如何找到一个pci设备的中断: IO_APIC_get_PCI_irq_vector, 就是这个函数, 顺这个线索, 可以知道apic模式下PCI中断获取的主要数据结构在arch/x86/kernel/io_apic_32.c(2.6.27):
/*
 * # of IRQ routing registers
 */
int nr_ioapic_registers[MAX_IO_APICS]; (32bit下为64)

/* I/O APIC entries */
struct mp_config_ioapic mp_ioapics[MAX_IO_APICS];
int nr_ioapics;

/* MP IRQ source entries */
struct mp_config_intsrc mp_irqs[MAX_IRQ_SOURCES]; (256)

/* # of MP IRQ source entries */
int mp_irq_entries;


另 外提下 全局位图io_apic_irqs, 其含义是IRQ<16的IRQ中哪些IRQ是链接到io APIC的. 原因是即便使用了APIC模式, 某些板子的有些中断也没有链接到IO APIC, 不可通过IO APIC路由. 下面的函数值得一读, 可以大致了解这些个表的主要内容.

* 从一个设备计算其irq的过程
int IO_APIC_get_PCI_irq_vector(int bus, int slot, int pin) /*通过IOAPIC查找bus/slot/pin 链接的IRQ*/
{
    int apic, i, best_guess = -1;

    apic_printk(APIC_DEBUG, "querying PCI -> IRQ mapping bus:%d, "
        "slot:%d, pin:%d.\n", bus, slot, pin);
    if (test_bit(bus, mp_bus_not_pci)) {
        printk(KERN_WARNING "PCI BIOS passed nonexistent PCI bus %d!\n", bus);
        return -1;
    }
    for (i = 0; i < mp_irq_entries; i++) { /*查找所有探测到的irq entry*/
        int lbus = mp_irqs[i].mp_srcbus;

        for (apic = 0; apic < nr_ioapics; apic++) /*这个irq entry 对应的ioapic entry*/
            if (mp_ioapics[apic].mp_apicid == mp_irqs[i].mp_dstapic ||
                mp_irqs[i].mp_dstapic == MP_APIC_ALL)
                break;

        if (!test_bit(lbus, mp_bus_not_pci) && /*必须是PCI bus*/
            !mp_irqs[i].mp_irqtype &&  /*iqr type */
            (bus == lbus) && /*bus/slot/pin相等*/
            (slot == ((mp_irqs[i].mp_srcbusirq >> 2) & 0x1f))) { /*MP SPEC 指出
srcbusirq中是slot pin*/
            int irq = pin_2_irq(i, apic, mp_irqs[i].mp_dstirq);  /*dstirq是IOAPIC的引脚编号,即INTIx#*/
                 /*计算所得是对应PIC模式irq的GSI*/
            if (!(apic || IO_APIC_IRQ(irq)))
                continue;

            if (pin == (mp_irqs[i].mp_srcbusirq & 3)) /*低2bit 是pin*/
                return irq;
            /*
             * Use the first all-but-pin matching entry as a
             * best-guess fuzzy result for broken mptables.
             */
            if (best_guess < 0)
                best_guess = irq;
        }
    }
    return best_guess;
}




*驱动request irq的背后
pci设备中有irq这一项, 注册给linux就行. 这个背后有点内容.  首先是APIC建立的"硬件链接":
pci 中断pin:INTx# --> IOAPIC INTINx#  :RTE 指定cpu的vector ---> X86的intr gate
linux软件:
x86 intr gate -> do_IRQ -> 驱动根据dev.irq 注册的处理函数
1) 什么时候给IOAPIC RTE分配vector?   参考函数:setup_IO_APIC_irqs -> assign_irq_vector.
2) 什么时候吧gate 指向do_IRQ的? 还是setup_IO_APIC_irqs -> ioapic_register_intr -> set_intr_gate(vector, interrupt[irq]);  而interrup已经准备好了, 参考 arch/x86/kernel/entry_32.S 如下代码:
/*
 * Build the entry stubs and pointer table with
 * some assembler magic.
 */
.section .rodata,"a",@progbits
ENTRY(interrupt)
.text

ENTRY(irq_entries_start)
    RING0_INT_FRAME
vector=0
.rept NR_IRQS
    ALIGN
 .if vector
    CFI_ADJUST_CFA_OFFSET -4
 .endif
1:    pushl $~(vector)
    CFI_ADJUST_CFA_OFFSET 4
    jmp common_interrupt
 .previous
    .long 1b
 .text
vector=vector+1
.endr
END(irq_entries_start)

   
然后  common_interrupt 跳转到do_IRQ去.
3) dev->irq这个值到底是什么?   PIC时代是IRQ, APIC时代也是IRQ, 或者叫做GSI. 多个apic会连续的编码成GSI,就是这个irq. (注意, 一个IOAPIC自己的引脚编号叫做INTIx#)



* 最后看看linux如何获取ioapic的各种entry
参考early_acpi_boot_init, acpi_boot_init. APIC定义了一堆堆的表格, 还有MP spec, 上面提到的管理表格其实类似MP spec定义的表格. 具体的就算了. 搂一眼就得了.





上一篇:LR SP PC
下一篇:PPM4 fails on Windows systems for users with non-A