Linux 2.6.36 x86 內核中斷初始化過程詳解

隨着硬件技術的發展,中斷控制芯片已經不再是傳統的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將特殊的設備中斷服務程序添加到對應的中斷請求結構體中,就能實現我們需要的中斷了。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章