2009年3月17日 星期二

Linux Interrupts and Exceptions - 3

I/O Interrupt (硬體裝置的中斷) 處理函式是以 request_irq() 來向核心註冊的,並且以 free_irq() 來清除

定義於 include/linux/interrupt.h

int request_irq(
    unsigned int  irq,
    irqreturn_t   (*handler)(int, void *, struct pt_regs *),
    unsigned long  irqflags,
    const char   *devname,
    void      *dev_id
  );

void free_irq(
    unsigned int irq,
    void     *dev_id
  );

struct irqaction {
  irqreturn_t (*handler)(int, void *, struct pt_regs *);
  unsigned long flags;
  cpumask_t mask;
  const char *name;
  void *dev_id;
  struct irqaction *next;
  int irq;
  struct proc_dir_entry *dir;
};

request_irq() 函式內部則是呼叫 setup_irq() 來進行 IRQ Line 的註冊
其動作是將要求中斷處理的資訊包裝成 struct irqaction 的結構,然後記錄於 irq_desc 陣列中的 IRQ Line 的位置
若有共用 IRQ Line 的中斷,則會以鏈結串列的方式掛在同一個陣列的位置上,如下圖所示

圖中的 hw_interrupt_type 是一個描述中斷控制器的資料結構,用來抽象化不同的中斷控制硬體
例如:舊式的 8259A,或新式的 APIC,也可能是無任何中斷控制器

定義於 include/linux/irq.h

/*
* Interrupt controller descriptor. This is all we need
* to describe about the low-level hardware.
*/
struct hw_interrupt_type {
  const char * typename;
  unsigned int (*startup)(unsigned int irq);
  void (*shutdown)(unsigned int irq);
  void (*enable)(unsigned int irq);
  void (*disable)(unsigned int irq);
  void (*ack)(unsigned int irq);
  void (*end)(unsigned int irq);
  void (*set_affinity)(unsigned int irq, cpumask_t dest);
};

typedef struct hw_interrupt_type hw_irq_controller;

定義於 arch/i386/kernel/i8259.c

static struct hw_interrupt_type i8259A_irq_type = {
  "XT-PIC",
  startup_8259A_irq,
  shutdown_8259A_irq,
  enable_8259A_irq,
  disable_8259A_irq,
  mask_and_ack_8259A,
  end_8259A_irq,
  NULL
};

定義於 arch/i386/kernel/io_apic.c

/*
* Level and edge triggered IO-APIC interrupts need different handling,
* so we use two separate IRQ descriptors. Edge triggered IRQs can be
* handled with the level-triggered descriptor, but that one has slightly
* more overhead. Level-triggered interrupts cannot be handled with the
* edge-triggered handler, without risking IRQ storms and other ugly
* races.
*/
static struct hw_interrupt_type ioapic_edge_type = {
  .typename  = "IO-APIC-edge",
  .startup   = startup_edge_ioapic,
  .shutdown  = shutdown_edge_ioapic,
  .enable    = enable_edge_ioapic,
  .disable   = disable_edge_ioapic,
  .ack     = ack_edge_ioapic,
  .end    = end_edge_ioapic,
  .set_affinity = set_ioapic_affinity,
};

static struct hw_interrupt_type ioapic_level_type = {
  .typename   = "IO-APIC-level",
  .startup   = startup_level_ioapic,
  .shutdown   = shutdown_level_ioapic,
  .enable    = enable_level_ioapic,
  .disable   = disable_level_ioapic,
  .ack     = mask_and_ack_level_ioapic,
  .end     = end_level_ioapic,
  .set_affinity = set_ioapic_affinity,
};

定義於 kernel/irq/handle.c

struct hw_interrupt_type no_irq_type = {
  .typename = "none",
  .startup = startup_none,
  .shutdown = shutdown_none,
  .enable = enable_none,
  .disable = disable_none,
  .ack = ack_none,
  .end = end_none,
  .set_affinity = NULL
};

2009年3月6日 星期五

Linux Interrupts and Exceptions - 2

下圖為 x86 處理器的 256 個中斷配置,其中 vector 編號 0-19 是前面提過的 Exception
編號 32-127 是硬體裝置的 I/O Interrupt,而編號 128 則是由 Linux 核心作為 System Call 之用


在 PC 架構中,有幾個裝置的中斷訊號必須分配到中斷控制器的固定 IRQ Line
如下圖的 IRQ 0、2 及 13
IRQ 欄位表示硬體的 IRQ Line,INT 欄位表示 Linux 核心使用的 IRQ Number

上述的 I/O Interrupt 及其處理函式被儲存於 Linux 核心的 irq_desc 陣列中
變數型態為 struct irq_desc (或 irq_desc_t),陣列大小為 NR_IRQS

定義於 kernel/irq/handle.c

irq_desc_t irq_desc[NR_IRQS] __cacheline_aligned = {
  [0 ... NR_IRQS-1] = {
    .handler = &no_irq_type,
    .lock = SPIN_LOCK_UNLOCKED
  }
};


定義於 include/linux/irq.h

typedef struct irq_desc {
  hw_irq_controller *handler;
  void *handler_data;
  struct irqaction *action;  /* IRQ action list */
  unsigned int status;    /* IRQ status */
  unsigned int depth;    /* nested irq disables */
  unsigned int irq_count;  /* For detecting broken interrupts */
  unsigned int irqs_unhandled;
  spinlock_t lock;
} ____cacheline_aligned irq_desc_t;


定義於 include/asm-i386/mach-default/irq_vectors_limits.h

#ifdef CONFIG_X86_IO_APIC
#define NR_IRQS 224
# if (224 >= 32 * NR_CPUS)
# define NR_IRQ_VECTORS NR_IRQS
# else
# define NR_IRQ_VECTORS (32 * NR_CPUS)
# endif
#else
#define NR_IRQS 16
#define NR_IRQ_VECTORS NR_IRQS
#endif


以上可看出,若系統使用 APIC 來管理裝置中斷時,IRQ 的個數為 224 個,否則為 16 個
當系統初始化時,核心呼叫 init_IRQ() 函式將 irq_desc 陣列中的 status 欄位設定為 IRQ_DISABLED
並且呼叫 set_intr_gate() 函式設定 CPU 的 Interrupt Gate,這個動作就是設定中斷發生時的處理函式

定義於 arch/i386/kernel/i8259.c

void __init init_IRQ(void)
{
  int i;

  /* all the set up before the call gates are initialised */
  pre_intr_init_hook();

  /*
   * Cover the whole vector space, no vector can escape
   * us. (some of these will be overridden and become
   * 'special' SMP interrupts)
   */
  for (i = 0; i < (NR_VECTORS - FIRST_EXTERNAL_VECTOR); i++) {
    int vector = FIRST_EXTERNAL_VECTOR + i;
    if (i >= NR_IRQS)
      break;
    if (vector != SYSCALL_VECTOR)
      set_intr_gate(vector, interrupt[i]);
  }

  /* setup after call gates are initialised (usually add in
   * the architecture specific gates)
   */
  intr_init_hook();

  /*
   * Set the clock to HZ Hz, we already have a valid
   * vector now:
   */
  setup_pit_timer();

  /*
   * External FPU? Set up irq13 if so, for
   * original braindamaged IBM FERR coupling.
   */
  if (boot_cpu_data.hard_math && !cpu_has_fpu)
    setup_irq(FPU_IRQ, &fpu_irq);

  irq_ctx_init(smp_processor_id());
}


void __init init_ISA_irqs (void)
{
  int i;

#ifdef CONFIG_X86_LOCAL_APIC
  init_bsp_APIC();
#endif
  init_8259A(0);

  for (i = 0; i < NR_IRQS; i++) {
    irq_desc[i].status = IRQ_DISABLED;
    irq_desc[i].action = NULL;
    irq_desc[i].depth = 1;

    if (i < 16) {
      /*
       * 16 old-style INTA-cycle interrupts:
       */
      irq_desc[i].handler = &i8259A_irq_type;
    } else {
      /*
       * 'high' PCI IRQs filled in on demand
       */
      irq_desc[i].handler = &no_irq_type;
    }
  }
}