linux pcie设备中断冲突,linux设备驱动之pci设备的中断请求

由上图可以看到.所有中线引脚都是连在中断请求路径互连器(下面简称为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和设备内存的分配问题.

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值