文章详情

  • 游戏榜单
  • 软件榜单
关闭导航
热搜榜
热门下载
热门标签
php爱好者> php文档>linux设备驱动之pci设备的中断请求

linux设备驱动之pci设备的中断请求

时间:2010-05-09  来源:p2pt

一:前言 经过上一节的分析,对pci有了一个大概的了解.我们今天来讨论一下pci设备的中断号确定,顺便解决上节中遗留的问题. 二:pci interrupt routing 我们在上面的分析过程中可以看到.pci的配置空间有两个寄存器,一个是IRQ_PIN.表示该pci设备所有的中断连接引脚.如果为0.说明没有引脚与中断线相连,也就是不支持中断. 另外一个是IRQ_LINE.表示该pci设备的引脚所连的中断线.也就是该设备对应的中断号. 其实在pci架构中,pci设备都是经过一个中断请求路径互联器的设备与8259A相联的.下图是摘自<<linux内核源代码情景分析>>上的图): 由上图可以看到.所有中线引脚都是连在中断请求路径互连器(下面简称为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和设备内存的分配问题.
相关阅读 更多 +
排行榜 更多 +
马里奥赛车世界游戏手机版下载

马里奥赛车世界游戏手机版下载

赛车竞速 下载
无畏契约皮肤开箱器手游下载

无畏契约皮肤开箱器手游下载

休闲益智 下载
旭日之城官方正版下载

旭日之城官方正版下载

策略塔防 下载