红联Linux门户
Linux帮助

Linux 2.6.36 x86内核中断初始化过程详解

发布时间:2015-01-07 10:34:40来源:linux网站作者:yearn520

随着硬件技术的发展,中断控制芯片已经不再是传统的ISA总线连着的简单PIC了,APIC,MSI,MSIX等等的词语大家已经非常的熟悉。同时,Linux内核也在不断发展,它在中断上的实现也越来越复杂,在这里我来讨论介绍一下Linux x86 架构下的中断初始化过程。

在start_kernel()之前的中断门初始化就不多啰嗦了,在随便的内核教科书里都能看到,这里就从start_kernel以后开始。


1.8259a、LAPIC相关数据结构初始化

在Linux之中对于每一个中断有两个重要的数据结构与之对应,他们分别是中断门描述符gate_desc和中断请求描述符irq_desc。

我们所谓的中断初始化也就是对这两个数据结构进行初始化。

gate_desc

gate_desc很简单,就是一个有着高CPL的普通门描述符。关键就是有一个成员是中断门函数地址,这个地址我们可以直接保存我们的ISR(中断服务程序),也可以保存一个一般中断的入口地址,然后通过这个入口地址指向irq_desc里面保存的ISR,的确Linux就是这样分多种情况实现的。

irq_desc

irq_desc里面成员就非常多并且复杂了,由于本文主要描述中断初始化过程,所以对触发过程顺便带过,关于中断的各种触发方式和处理过程会在其他文章中详细讲述。

struct irq_desc {
 unsigned int  irq;
 unsigned int  *kstat_irqs;
 irq_flow_handler_t handle_irq;

 struct irqaction *action;

 struct irq_chip  *chip;
 struct msi_desc  *msi_desc;
 void*handler_data;
 void*chip_data;
 const char  *name;

 ...

 ...s
}

其中中断芯片结构指针*chip,中断触发方式 handle_irq,以及中断服务程序链表*action。

中断初始化路径:start_kernel() -> init_IRQ() -> native_init_IRQ() ->

 void __init native_init_IRQ(void)
{
int i;

/* 初始化8259a PIC中断控制器相关数据结构*/
x86_init.irqs.pre_vector_init();

 /* 初始化SMP的APIC中断门描述符*/
 apic_intr_init();

 /*在这里进行遍历所有的中断门,将所有还没有配置得中断门进行统一配置,在这里有一个interrupt函数数组指针,当中断发生的时候将会触发这个interrupt函数,然后所有的interrupt都会从调用do_IRQ的函数,在do_IRQ()里面触发真正的中断服务程序。 */
 for (i = FIRST_EXTERNAL_VECTOR; i < NR_VECTORS; i++) {
  if (!test_bit(i, used_vectors))
 set_intr_gate(i, interrupt[i-FIRST_EXTERNAL_VECTOR]);
 }
}


(1)初始化8259a相关

void __init init_ISA_irqs(void)
{
int i;

/*初始化PIC和local APIC芯片,通过写IO,对芯片进行初始化。*/

 #if defined(CONFIG_X86_64) || defined(CONFIG_X86_LOCAL_APIC)
init_bsp_APIC();
 #endif
init_8259A(0);

/*在这里将8259a控制的16个中断进行芯片初始化。并且设置电平触发方式,这样是对上面说的irq_desc这个结构进行初始化,当然这里只是PIC芯片部分。*/

for (i = 0; i < NR_IRQS_LEGACY; i++) {
struct irq_desc *desc = irq_to_desc(i);

desc->status = IRQ_DISABLED;
desc->action = NULL;
desc->depth = 1;

set_irq_chip_and_handler_name(i, &i8259A_chip,
 handle_level_irq, "XT");
}
}


 (2)Apic默认基本中断门初始化。

static void __init apic_intr_init(void)
{
smp_intr_init();

 #ifdef CONFIG_X86_THERMAL_VECTOR
alloc_intr_gate(THERMAL_APIC_VECTOR, thermal_interrupt);
 #endif
 #ifdef CONFIG_X86_MCE_THRESHOLD
alloc_intr_gate(THRESHOLD_APIC_VECTOR, threshold_interrupt);
 #endif
 #if defined(CONFIG_X86_MCE) && defined(CONFIG_X86_LOCAL_APIC)
alloc_intr_gate(MCE_SELF_VECTOR, mce_self_interrupt);
 #endif

 #if defined(CONFIG_X86_64) || defined(CONFIG_X86_LOCAL_APIC)
/* self generated IPI for local APIC timer */
alloc_intr_gate(LOCAL_TIMER_VECTOR, apic_timer_interrupt);

/* IPI for X86 platform specific use */
alloc_intr_gate(X86_PLATFORM_IPI_VECTOR, x86_platform_ipi);

/* IPI vectors for APIC spurious and error interrupts */
alloc_intr_gate(SPURIOUS_APIC_VECTOR, spurious_interrupt);
alloc_intr_gate(ERROR_APIC_VECTOR, error_interrupt);

/* Performance monitoring interrupts: */
 # ifdef CONFIG_PERF_EVENTS
alloc_intr_gate(LOCAL_PENDING_VECTOR, perf_pending_interrupt);
 # endif

 #endif
}

我们可以看到这些中断初始化很特别,就像我们设置的陷阱门系统调用一样,将中断处理函数直接放在中断门得指向地址,这样只要中断到来,一旦通过中断门将直接跳像中断处理函数,而忽略了irq_desc部分,不需要考虑怎么触发,不需要考虑怎么调度。我们在Linux用

cat /proc/interrupts 也可以看到这些中断与众不同。这些中断左边显示的不是中断请求号,而是一个标识,右边显示的也不是中断控制芯片的信息。


(3)剩余中断门得初始化

初始化完特殊的中断以后,接着就将剩余的中断门统一进行初始化,执行统一的interrupt出口。这个就是上面说的gate_desc结构体的大部分初始化地方。


2.IO-APIC相关数据结构初始化

之前我们讲到了8259a PIC中断芯片以及local APIC初始化,然后接着是IO-APIC中断分发芯片结构体初始化。

初始化路径:start_kernel() -> rest_init -> kernel_thread() -> kernel -> smp_init() -> APIC_init_uniprocessor() -> setup_IO_APIC() -> setup_IO_APIC_irqs() -> ioapic_register_intr()


(1)在setup_IO_APIC_irqs()函数中,将所有注册了的IOAPIC中断请求结构体进行遍历,注册中断默认类型等信息。

static void __init setup_IO_APIC_irqs(void)
{
 int apic_id = 0, pin, idx, irq;
 int notcon = 0;
 struct irq_desc *desc;
 struct irq_cfg *cfg;
 int node = cpu_to_node(boot_cpu_id);

 apic_printk(APIC_VERBOSE, KERN_DEBUG "init IO_APIC IRQs\n");

#ifdef CONFIG_ACPI
 if (!acpi_disabled && acpi_ioapic) {
  apic_id = mp_find_ioapic(0);
  if (apic_id < 0)
apic_id = 0;
 }
#endif

 //遍历所有的注册中断请求设备

 for (pin = 0; pin < nr_ioapic_registers[apic_id]; pin++) {
  idx = find_irq_entry(apic_id, pin, mp_INT);
  if (idx == -1) {
if (!notcon) {
 notcon = 1;
 apic_printk(APIC_VERBOSE,
KERN_DEBUG " %d-%d",
mp_ioapics[apic_id].apicid, pin);
} else
 apic_printk(APIC_VERBOSE, " %d-%d",
mp_ioapics[apic_id].apicid, pin);
continue;
  }
  if (notcon) {
apic_printk(APIC_VERBOSE,
 " (apicid-pin) not connected\n");
notcon = 0;
  }

  irq = pin_2_irq(idx, apic_id, pin);

  /*
* Skip the timer IRQ if there's a quirk handler
* installed and if it returns 1:
*/
  if (apic->multi_timer_check &&
 apic->multi_timer_check(apic_id, irq))
continue;

  desc = irq_to_desc_alloc_node(irq, node);
  if (!desc) {
printk(KERN_INFO "can not get irq_desc for %d\n", irq);
continue;
  }
  cfg = desc->chip_data;
  add_pin_to_irq_node(cfg, node, apic_id, pin);
  /*
* don't mark it in pin_programmed, so later acpi could
* set it correctly when irq < 16
*/

  //设置中断触发类型
  setup_IO_APIC_irq(apic_id, pin, irq, desc,
 irq_trigger(idx), irq_polarity(idx));
 }

 if (notcon)
  apic_printk(APIC_VERBOSE,
" (apicid-pin) not connected\n");
}


 (2)注册ioapic芯片,如果和8259有冲突就覆盖。

static void setup_IO_APIC_irq(int apic_id, int pin, unsigned int irq, struct irq_desc *desc,
 int trigger, int polarity)
{
 struct irq_cfg *cfg;
 struct IO_APIC_route_entry entry;
 unsigned int dest;

 if (!IO_APIC_IRQ(irq))
  return;

 cfg = desc->chip_data;

 if (assign_irq_vector(irq, cfg, apic->target_cpus()))
  return;

 dest = apic->cpu_mask_to_apicid_and(cfg->domain, apic->target_cpus());

 apic_printk(APIC_VERBOSE,KERN_DEBUG
 "IOAPIC[%d]: Set routing entry (%d-%d -> 0x%x -> "
 "IRQ %d Mode:%i Active:%i)\n",
 apic_id, mp_ioapics[apic_id].apicid, pin, cfg->vector,
 irq, trigger, polarity);

 if (setup_ioapic_entry(mp_ioapics[apic_id].apicid, irq, &entry,
dest, trigger, polarity, cfg->vector, pin)) {
  printk("Failed to setup ioapic entry for ioapic  %d, pin %d\n",
 mp_ioapics[apic_id].apicid, pin);
  __clear_irq_vector(irq, cfg);
  return;
 }

 //注册中断芯片,覆盖之前初始化的8259芯片。

 ioapic_register_intr(irq, desc, trigger);
 if (irq < nr_legacy_irqs)
  disable_8259A_irq(irq);

 ioapic_write_entry(apic_id, pin, entry);
}

static void ioapic_register_intr(int irq, struct irq_desc *desc, unsigned long trigger)
{

 if ((trigger == IOAPIC_AUTO && IO_APIC_irq_trigger(irq)) ||
trigger == IOAPIC_LEVEL)
  desc->status |= IRQ_LEVEL;
 else
  desc->status &= ~IRQ_LEVEL;

 if (irq_remapped(irq)) {
  desc->status |= IRQ_MOVE_PCNTXT;
  if (trigger)
set_irq_chip_and_handler_name(irq, &ir_ioapic_chip,
  handle_fasteoi_irq,
 "fasteoi");
  else
set_irq_chip_and_handler_name(irq, &ir_ioapic_chip,
  handle_edge_irq, "edge");
  return;
 }

 if ((trigger == IOAPIC_AUTO && IO_APIC_irq_trigger(irq)) ||
trigger == IOAPIC_LEVEL)
  set_irq_chip_and_handler_name(irq, &ioapic_chip,
 handle_fasteoi_irq,
 "fasteoi");
 else
  set_irq_chip_and_handler_name(irq, &ioapic_chip,
 handle_edge_irq, "edge");
}

到这里,我们中断请求的数据结构irq_desc也基本初始化完了,每个中断都分配了默认的处理控制芯片,默认的触发方式,和共同的触发函数入口,而我们平时写驱动的时候就可以直接调用request_irq将特殊的设备中断服务程序添加到对应的中断请求结构体中,就能实现我们需要的中断了。