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 规范中摘出的一个系统组成图: P35 芯片组 我的机器是p35芯片组的,可以在这里找到芯片手册: 这里首先搞清楚北桥芯片内的几个主要设备:
感觉如下理解这些关系比较好: 从配置上是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关系 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的工作很有意义..
恩, 其实不复杂,
在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的寄存器, 描述这个信息. 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定义的表格. 具体的就算了. 搂一眼就得了. |