------------------------------------------
本文系本站原创,欢迎转载!
转载请注明出处:http://ericxiao.cublog.cn/
------------------------------------------
一:前言
经过上一节的分析,对pci有了一个大概的了解.我们今天来讨论一下pci设备的中断号确定,顺便解决上节中遗留的问题.
二:pci interrupt routing
我们在上面的分析过程中可以看到.pci的配置空间有两个寄存器,一个是IRQ_PIN.表示该pci设备所有的中断连接引脚.如果为0.说明没有引脚与中断线相连,也就是不支持中断. 另外一个是IRQ_LINE.表示该pci设备的引脚所连的中断线.也就是该设备对应的中断号.
其实在pci架构中,pci设备都是经过一个中断请求路径互联器的设备与8259A相联的.下图是摘自<<linux内核源代码情景分析>>上的图):
![](http://blogimg.chinaunix.net/blog/upfile2/080821161906.jpg)
由上图可以看到.所有中线引脚都是连在中断请求路径互连器(下面简称为PIR)上的,然后再由它控制到8259A的连接.有一点要特别注意.在APIC中是没有PIR的,因为APIC本身就是一个可编程控制器.在本节的讨论中,我们只讨论8259A的情况.
至于中断控制器的输入引脚是和哪一个8259A的IRQ线相联.这是由PIR芯片所控制的.
闲言少述,我们从代码中看一下是怎样处理这个IRQ号的.相应的处理是在pcibios_irq_init()中完成的.先看下它的调用方式:
subsys_initcall(pcibios_irq_init);
pcibios_irq_init()是在subsys_initcall宏被调用的.在编译的时候被放至了init区域,kernel启动的时候会调用此函数.
在分析函数之前,我们先要有以下几点基本的概念:
1: PIR其实也是一个PCI设备.所以它也有对应的总线号,设备号等.也可以通过PCI寻址方式对它进行配置
2: 在确定PCI设备的中断号之前,我们需要知道.PCI设备是连在PIR的哪一个输入线上,这也是一个复杂的过程,幸好bios为我们提供了这样的功能.
好了,转入具体的代码:
static int __init pcibios_irq_init(void)
{
DBG(KERN_DEBUG "PCI: IRQ init\n");
if (pcibios_enable_irq || raw_pci_ops == NULL)
return 0;
dmi_check_system(pciirq_dmi_table);
pirq_table = pirq_find_routing_table();
#ifdef CONFIG_PCI_BIOS
if (!pirq_table && (pci_probe & PCI_BIOS_IRQ_SCAN))
pirq_table = pcibios_get_irq_routing_table();
#endif
if (pirq_table) {
pirq_peer_trick();
pirq_find_router(&pirq_router);
if (pirq_table->exclusive_irqs) {
int i;
for (i=0; i<16; i++)
if (!(pirq_table->exclusive_irqs & (1 << i)))
pirq_penalty[i] += 100;
}
/* If we're using the I/O APIC, avoid using the PCI IRQ routing table */
if (io_apic_assign_pci_irqs)
pirq_table = NULL;
}
pcibios_enable_irq = pirq_enable_irq;
pcibios_fixup_irqs();
return 0;
}
函数先调用pirq_find_routing_table()来寻找bios提供的中断路径表,代码如下 :
static struct irq_routing_table * __init pirq_find_routing_table(void)
{
u8 *addr;
struct irq_routing_table *rt;
if (pirq_table_addr) {
rt = pirq_check_routing_table((u8 *) __va(pirq_table_addr));
if (rt)
return rt;
printk(KERN_WARNING "PCI: PIRQ table NOT found at pirqaddr\n");
}
for(addr = (u8 *) __va(0xf0000); addr < (u8 *) __va(0x100000); addr += 16) {
rt = pirq_check_routing_table(addr);
if (rt)
return rt;
}
return NULL;
}
如果指定了中断路径表的地址.那直接判断它是否合法就可以了.如果没有指定,那就需要搜索bios的映射区了.
转入pirq_check_routing_table().代码如下:
static inline struct irq_routing_table * pirq_check_routing_table(u8 *addr)
{
struct irq_routing_table *rt;
int i;
u8 sum;
rt = (struct irq_routing_table *) addr;
if (rt->signature != PIRQ_SIGNATURE ||
rt->version != PIRQ_VERSION ||
//是否低四位对齐
rt->size % 16 ||
rt->size < sizeof(struct irq_routing_table))
return NULL;
sum = 0;
//所有的值加起来必须为零.因为它后面多了一个checksum
for (i=0; i < rt->size; i++)
sum += addr[i];
if (!sum) {
DBG(KERN_DEBUG "PCI: Interrupt Routing Table found at 0x%p\n", rt);
return rt;
}
return NULL;
}
Bios提供的中断路径表有自己的格式.对应格式的结构如下所示 :
struct irq_routing_table {
//恒定义为PIRQ_SIGNATURE
u32 signature; /* PIRQ_SIGNATURE should be here */
//恒为PIRQ_VERSION
u16 version; /* PIRQ_VERSION */
//表的大小
u16 size; /* Table size in bytes */
//PIR的总线号和设备功能号
u8 rtr_bus, rtr_devfn; /* Where the interrupt router lies */
//分配给PIR专用的IRQ
u16 exclusive_irqs; /* IRQs devoted exclusively to PCI usage */
//PIR芯片的厂商和设备号
u16 rtr_vendor, rtr_device; /* Vendor and device ID of interrupt router */
//没有用到
u32 miniport_data; /* Crap */
//保留区
u8 rfu[11];
//检验和.表头与检验之和为0
u8 checksum; /* Modulo 256 checksum must give zero */
//中断路径表项,包含了每个设备对应的输入引脚和可用的IRQ号等信息
struct irq_info slots[0];
}
注意上面的结构中.最后一项是一个0项的数组,这个是为以后做扩展用的,
Struct irq_info结构如下 :
struct irq_info {
//设备的总线号和设备功能号
u8 bus, devfn; /* Bus, device and function */
//pci设备的每个引脚对应的输入线和可能的IRQ号
struct {
u8 link; /* IRQ line ID, chipset dependent, 0=not routed */
u16 bitmap; /* Available IRQs */
} __attribute__((packed)) irq[4];
u8 slot; /* Slot number, 0=onboard */
u8 rfu;
} __attribute__((packed));
在pirq_check_routing_table()中判断给定的地址是否存放中断路径表.它从它的固定标识,版本号和检验和进行判断.
回到pcibios_irq_init()中.它将在bios中寻找到的中断路径表存放于全局变量pirq_table中.然后再调用pirq_peer_trick().
现在这个中断路径表里已经包含和所有pci设备的信息了.可以从这些信息里找出.有那些根总线是我们没有枚举过的.这也就是pirq_peer_trick()要处理的事情.
static void __init pirq_peer_trick(void)
{
struct irq_routing_table *rt = pirq_table;
u8 busmap[256];
int i;
struct irq_info *e;
memset(busmap, 0, sizeof(busmap));
for(i=0; i < (rt->size - sizeof(struct irq_routing_table)) / sizeof(struct irq_info); i++) {
e = &rt->slots[i];
#ifdef DEBUG
{
int j;
DBG(KERN_DEBUG "%02x:%02x slot=%02x", e->bus, e->devfn/8, e->slot);
for(j=0; j<4; j++)
DBG(" %d:%02x/%04x", j, e->irq[j].link, e->irq[j].bitmap);
DBG("\n");
}
#endif
busmap[e->bus] = 1;
}
for(i = 1; i < 256; i++) {
if (!busmap[i] || pci_find_bus(0, i))
continue;
if (pci_scan_bus_with_sysdata(i))
printk(KERN_INFO "PCI: Discovered primary peer "
"bus %02x [IRQ]\n", i);
}
pcibios_last_bus = -1;
}
先在中断路径表里找到所用到的总线号,如果该总线出现在我们之前遍历出的pci_root_buses中.那它就是属于新增的.枚举它.
在这里要注意一点,可能我们还有其余的根总线没有枚举到.那它之下的次总线也不会出现在pci_root_buses中.我们应该先遍历根总线,然后再来遍来其下的次总线.
所以上面的后一个循环是从总线的小值向大值循环,因为根总线的总线号会小于它下面的子层总线号.
转入pci_scan_bus_with_sysdata() :
struct pci_bus *__devinit pci_scan_bus_with_sysdata(int busno)
{
struct pci_bus *bus = NULL;
struct pci_sysdata *sd;
/*
* Allocate per-root-bus (not per bus) arch-specific data.
* TODO: leak; this memory is never freed.
* It's arguable whether it's worth the trouble to care.
*/
sd = kzalloc(sizeof(*sd), GFP_KERNEL);
if (!sd) {
printk(KERN_ERR "PCI: OOM, skipping PCI bus %02x\n", busno);
return NULL;
}
sd->node = -1;
bus = pci_scan_bus(busno, &pci_root_ops, sd);
if (!bus)
kfree(sd);
return bus;
}
pci_scan_bus()我们在上节已经分析过了.它以深度优先搜索算法枚举该总线下的设备.
总线全部都遍历完之后,在pirq_peer_trick()中会将pcibios_last_bus设为-1.表示所有总线都已经遍历完了.
在pcibios_irq_init()中,调用完pirq_peer_trick()之后还会调用pirq_find_router().它为PIR设备取得驱动类信息,我们要知道哪一条输入线是对应哪一个IRQ号,这是跟具体的芯片相关的,
代码如下:
static void __init pirq_find_router(struct irq_router *r)
{
struct irq_routing_table *rt = pirq_table;
struct irq_router_handler *h;
#ifdef CONFIG_PCI_BIOS
if (!rt->signature) {
printk(KERN_INFO "PCI: Using BIOS for IRQ routing\n");
r->set = pirq_bios_set;
r->name = "BIOS";
return;
}
#endif
/* Default unless a driver reloads it */
r->name = "default";
r->get = NULL;
r->set = NULL;
DBG(KERN_DEBUG "PCI: Attempting to find IRQ router for %04x:%04x\n",
rt->rtr_vendor, rt->rtr_device);
pirq_router_dev = pci_get_bus_and_slot(rt->rtr_bus, rt->rtr_devfn);
if (!pirq_router_dev) {
DBG(KERN_DEBUG "PCI: Interrupt router not found at "
"%02x:%02x\n", rt->rtr_bus, rt->rtr_devfn);
return;
}
for( h = pirq_routers; h->vendor; h++) {
/* First look for a router match */
if (rt->rtr_vendor == h->vendor && h->probe(r, pirq_router_dev, rt->rtr_device))
break;
/* Fall back to a device match */
if (pirq_router_dev->vendor == h->vendor && h->probe(r, pirq_router_dev, pirq_router_dev->device))
break;
}
printk(KERN_INFO "PCI: Using IRQ router %s [%04x/%04x] at %s\n",
pirq_router.name,
pirq_router_dev->vendor,
pirq_router_dev->device,
pci_name(pirq_router_dev));
/* The device remains referenced for the kernel lifetime */
}
PIR也是一个PCI设备.因此在pci_devices链表中应该会找到它的信息.如果设备确实存在的话.就会根据它的厂商ID和设备ID来匹配相关的驱动.然后将驱动相关结构保存在struct irq_router结构中.该结构有get ,set两个指针.分别用来获取和设置关于输入线的中断号信息.
Linux暂支持的PIR厂商和设备信息包含在下面的数组中:
static __initdata struct irq_router_handler pirq_routers[] = {
{ PCI_VENDOR_ID_INTEL, intel_router_probe },
{ PCI_VENDOR_ID_AL, ali_router_probe },
{ PCI_VENDOR_ID_ITE, ite_router_probe },
{ PCI_VENDOR_ID_VIA, via_router_probe },
{ PCI_VENDOR_ID_OPTI, opti_router_probe },
{ PCI_VENDOR_ID_SI, sis_router_probe },
{ PCI_VENDOR_ID_CYRIX, cyrix_router_probe },
{ PCI_VENDOR_ID_VLSI, vlsi_router_probe },
{ PCI_VENDOR_ID_SERVERWORKS, serverworks_router_probe },
{ PCI_VENDOR_ID_AMD, amd_router_probe },
{ PCI_VENDOR_ID_PICOPOWER, pico_router_probe },
/* Someone with docs needs to add the ATI Radeon IGP */
{ 0, NULL }
}
再次返回pcibios_irq_init().将剩余的代码列出来,方便分析 :
static int __init pcibios_irq_init(void)
{
......
......
if (pirq_table) {
pirq_peer_trick();
pirq_find_router(&pirq_router);
if (pirq_table->exclusive_irqs) {
int i;
for (i=0; i<16; i++)
if (!(pirq_table->exclusive_irqs & (1 << i)))
pirq_penalty[i] += 100;
}
/* If we're using the I/O APIC, avoid using the PCI IRQ routing table */
if (io_apic_assign_pci_irqs)
pirq_table = NULL;
}
pcibios_enable_irq = pirq_enable_irq;
pcibios_fixup_irqs();
return 0;
}
将下来,就要来处理在PIR中,中断号的定向问题了.linux的处理方案中,因为有很多IRQ是共享的,因此尽量将它平均分配中各个IRQ线.另外,有些IRQ线是专用的.不能被打扰.这种平衡机制是通过pirq_penalty[ ]数组来完成的.它有16项,分别对应对16个IRQ号.定义如下 :
static int pirq_penalty[16] = {
1000000, 1000000, 1000000, 1000, 1000, 0, 1000, 1000,
0, 0, 0, 0, 1000, 100000, 100000, 100000
};
从它的定义中可以看到,它有三个值 : 100000, 1000,0.
定义为100000的IRQ根本就不会被PCI用到.相应IRQ为,0,1,2,14,15,16
定义为1000的使用机率较小
定义为0的是最可能会用到的.
在pcibios_irq_init()中的代码 :
pirq_table->exclusive_irqs表示的是由PIR芯片专用的IRQ掩码.它有16位.分别对应16个IRQ.如果相应位被置,则对应的IRQ是属于PIR专用的.
在上面的代码中看出.如果不是被PIR专用的IRQ号,都会被加上100.
转入到pcibios_fixup_irqs().代码较长,除去APIC的相关部份.如下 :
static void __init pcibios_fixup_irqs(void)
{
struct pci_dev *dev = NULL;
u8 pin;
DBG(KERN_DEBUG "PCI: IRQ fixup\n");
while ((dev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, dev)) != NULL) {
/*
* If the BIOS has set an out of range IRQ number, just ignore it.
* Also keep track of which IRQ's are already in use.
*/
if (dev->irq >= 16) {
DBG(KERN_DEBUG "%s: ignoring bogus IRQ %d\n", pci_name(dev), dev->irq);
dev->irq = 0;
}
/* If the IRQ is already assigned to a PCI device, ignore its ISA use penalty */
if (pirq_penalty[dev->irq] >= 100 && pirq_penalty[dev->irq] < 100000)
pirq_penalty[dev->irq] = 0;
pirq_penalty[dev->irq]++;
}
dev = NULL;
while ((dev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, dev)) != NULL) {
pci_read_config_byte(dev, PCI_INTERRUPT_PIN, &pin);
/*
* Still no IRQ? Try to lookup one...
*/
if (pin && !dev->irq)
pcibios_lookup_irq(dev, 0);
}
}
它遍历所有的PCI设备,如果IRQ号非法,就将其设为0.然后,如果PCI已经被指定了IRQ,且pirq_penalty[ ]中的对应项超过了100.则复为0.对应这种情况,只有在该IRQ号没有被PIR设备专用的且在pirq_penalty[ ]中原始定义为0才能”享受”这样的待遇.
最后,在第二次遍历的时候,如果有引脚连到PIR,但是中断号又非法,就会调用pcibios_lookup_irq()进行修正.
这个函数有两个参数,一个是要修正的pci_dev.另外的一个参数是assign.如果为1.在没有为设备分配的IRQ的情况下,它会为之分配一下(没有分配IRQ.对应也就是输入引脚没有跟8259A相联).如果为0.就暂时不去管它.我们在下面的情况,把参数为0和1的情况全部分析.
代码比较长,分段分析如下:
static int pcibios_lookup_irq(struct pci_dev *dev, int assign)
{
u8 pin;
struct irq_info *info;
int i, pirq, newirq;
int irq = 0;
u32 mask;
struct irq_router *r = &pirq_router;
struct pci_dev *dev2 = NULL;
char *msg = NULL;
/* Find IRQ pin */
pci_read_config_byte(dev, PCI_INTERRUPT_PIN, &pin);
if (!pin) {
DBG(KERN_DEBUG " -> no interrupt pin\n");
return 0;
}
pin = pin - 1;
/* Find IRQ routing entry */
if (!pirq_table)
return 0;
DBG(KERN_DEBUG "IRQ for %s[%c]", pci_name(dev), 'A' + pin);
info = pirq_get_info(dev);
if (!info) {
DBG(" -> not found in routing table\n" KERN_DEBUG);
return 0;
}
pirq = info->irq[pin].link;
mask = info->irq[pin].bitmap;
if (!pirq) {
DBG(" -> not routed\n" KERN_DEBUG);
return 0;
}
DBG(" -> PIRQ %02x, mask %04x, excl %04x", pirq, mask, pirq_table->exclusive_irqs);
mask &= pcibios_irq_mask;
首先,在设备的IRQ_LIN寄存器中取得设备所用的中断线.之后再到之前找到的中断路径表去寻找该设备的相关信息.因为在中断路径表中,irq_info数组项是以0开始计数.因此需要把取得的引脚值减1.
中断路径表中包含设备中断线对应的输入引脚和所允许的IRQ值.
pcibios_irq_mask被定义成0xfff8.也就是说不允许使用0,1,2,3这四个IRQ号
/* Work around broken HP Pavilion Notebooks which assign USB to
IRQ 9 even though it is actually wired to IRQ 11 */
if (broken_hp_bios_irq9 && pirq == 0x59 && dev->irq == 9) {
dev->irq = 11;
pci_write_config_byte(dev, PCI_INTERRUPT_LINE, 11);
r->set(pirq_router_dev, dev, pirq, 11);
}
/* same for Acer Travelmate 360, but with CB and irq 11 -> 10 */
if (acer_tm360_irqrouting && dev->irq == 11 && dev->vendor == PCI_VENDOR_ID_O2) {
pirq = 0x68;
mask = 0x400;
dev->irq = r->get(pirq_router_dev, dev, pirq);
pci_write_config_byte(dev, PCI_INTERRUPT_LINE, dev->irq);
}
这两段是对应HP和Acer计算机的特殊处理,忽略
/*
* Find the best IRQ to assign: use the one
* reported by the device if possible.
*/
newirq = dev->irq;
if (newirq && !((1 << newirq) & mask)) {
if ( pci_probe & PCI_USE_PIRQ_MASK) newirq = 0;
else printk("\n" KERN_WARNING
"PCI: IRQ %i for device %s doesn't match PIRQ mask "
"- try pci=usepirqmask\n" KERN_DEBUG, newirq,
pci_name(dev));
}
if (!newirq && assign) {
for (i = 0; i < 16; i++) {
if (!(mask & (1 << i)))
continue;
if (pirq_penalty[i] < pirq_penalty[newirq] && can_request_irq(i, IRQF_SHARED))
newirq = i;
}
}
将设备指定使用的IRQ存放在newirq中,如果设备指定的IRQ不可用,则将newirq设为0. 如果newirq为零且调用参数为1,则为它分配一个新的IRQ.这个IRQ必须要是可共享的
DBG(" -> newirq=%d", newirq);
/* Check if it is hardcoded */
if ((pirq & 0xf0) == 0xf0) {
irq = pirq & 0xf;
DBG(" -> hardcoded IRQ %d\n", irq);
msg = "Hardcoded";
} else if ( r->get && (irq = r->get(pirq_router_dev, dev, pirq)) && \
((!(pci_probe & PCI_USE_PIRQ_MASK)) || ((1 << irq) & mask)) ) {
DBG(" -> got IRQ %d\n", irq);
msg = "Found";
eisa_set_level_irq(irq);
} else if (newirq && r->set && (dev->class >> 8) != PCI_CLASS_DISPLAY_VGA) {
DBG(" -> assigning IRQ %d", newirq);
if (r->set(pirq_router_dev, dev, pirq, newirq)) {
eisa_set_level_irq(newirq);
DBG(" ... OK\n");
msg = "Assigned";
irq = newirq;
}
}
1:如果输入引脚线的高四位为零,说明是一个硬连接.也就是说,从N号引脚进来,就连在8259A的N号引脚上.因此它的IRQ也就是输入引脚的低四位
2:到芯片中取得输入引脚对应的IRQ.如果返回失败,则说明该输入引脚线还没有连接上
3:调用芯片的set方法,将对应输入引脚连接到8259A的newirq号中断线上
由此可以看到,只有在设备指定IRQ为0的情况下,才会更改它的IRQ号,.否则,就会尽量使用它指定的IRQ.从这里也可以看到,函数的第二个参数,只有在设备指定的IRQ为0的时候才会有区别
if (!irq) {
DBG(" ... failed\n");
if (newirq && mask == (1 << newirq)) {
msg = "Guessed";
irq = newirq;
} else
return 0;
}
printk(KERN_INFO "PCI: %s IRQ %d for device %s\n", msg, irq, pci_name(dev));
/* Update IRQ for all devices with the same pirq value */
while ((dev2 = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, dev2)) != NULL) {
pci_read_config_byte(dev2, PCI_INTERRUPT_PIN, &pin);
if (!pin)
continue;
pin--;
info = pirq_get_info(dev2);
if (!info)
continue;
if (info->irq[pin].link == pirq) {
/* We refuse to override the dev->irq information. Give a warning! */
if ( dev2->irq && dev2->irq != irq && \
(!(pci_probe & PCI_USE_PIRQ_MASK) || \
((1 << dev2->irq) & mask)) ) {
#ifndef CONFIG_PCI_MSI
printk(KERN_INFO "IRQ routing conflict for %s, have irq %d, want irq %d\n",
pci_name(dev2), dev2->irq, irq);
#endif
continue;
}
dev2->irq = irq;
pirq_penalty[irq]++;
if (dev != dev2)
printk(KERN_INFO "PCI: Sharing IRQ %d with %s\n", irq, pci_name(dev2));
}
}
return 1;
}
由此上面可能更改了输入引脚线的对应的IRQ,如果有其它设备的输出引脚对应在这个输入引脚上,则同样需要更新它对应的IRQ.
到这里,我们能够得出每个设备对应的IRQ.事实上,到这个地方,只是更新了配置错误的pci设备,以及跟他们在同一条输入信上的设备的IRQ.其它的设备并没有更新.在linux中,把pci设备的IRQ更新推迟到设备要被使用的时候再来更新.因为有可能设备之后不会用到.
其它设备的IRQ更新是由其驱动完成的.一般都会在驱动程序里调用pirq_enable_irq()接口.对其的中断功能进行初始化.这部份我们在分析pci驱动架构的时候来统一分析.
三:小结
在这一小节里,讨论了怎样确定PCI设备的中断号.在下一节里,我们来讨论一下关于PCI设备的I/O和设备内存的分配问题.
转载于:https://blog.51cto.com/dongyulong/428075