x86從 start_kernel 開始的中斷初始化

以下主要看了 linux 3.2 中,從 start_kernel() 開始的一些跟中斷有關的初始化代碼,並做了一點點簡單的分析。start_kernel() 在 init/main.c 中,其中和中斷有關的大概就有這樣一些函數:

/* filename: init/main.c */

467 asmlinkage void __init start_kernel(void)
468 {
    	    ... ...
488         local_irq_disable();
	    ... ...
499	    setup_arch(&command_line);
	    ... ...
526 	    trap_init();
	    ... ...
550	    early_irq_init();
551 	    init_IRQ();
	    ... ...
564 	    local_irq_enable();

在進行各種初始化之前,要先關閉中斷,因爲這個時候調度程序等還沒有初始化,如果發生中斷,進行進程切換時會出現問題(?)。所以會調用 local_irq_disable() 來關閉中斷。local_irq_disable() 定義在:

/* filename: include/linux/irqflags.h */

59 #define raw_local_irq_disable()		arch_local_irq_disable()

137 #define local_irq_disable()		do { raw_local_irq_disable(); } while (0)

可以看出,不同的架構,其具體的 local_irq_disable() 實現各不相同。但都由 arch_local_irq_disable() 來統一調用。對於 x86_32 來說,arch_local_irq_disable() 的實現如下:

/* filename: arch/x86/include/asm/irqflags.h */

37 static inline void native_irq_disable(void)
38 {
39 	    asm volatile("cli": : :"memory");
40 }

75 static inline notrace void arch_local_irq_disable(void)
76 {
77  	    native_irq_disable();
78 }

這裏用內聯彙編的形式,直接調用 cli 指令來清除 EFLAGS 寄存器的中斷允許位,以關閉中斷。


Linux 內核在初始化階段完成了對頁式虛擬內存管理的初始化後,便調用 trap_init() 和 init_IRQ() 兩個函數進行中斷機制的初始化。trap_init() 主要是對一些系統保留的中斷向量進行初始化。而 init_IRQ() 則主要用於外設的中斷。函數 trap_init() 是在 arch/x86/kernel/traps.c 中定義的。

/* filename: arch/x86/kernel/traps.c */

669 void __init trap_init(void)
670 {
671         int i;
672 
673 #ifdef CONFIG_EISA
674         void __iomem *p = early_ioremap(0x0FFFD9, 4);
675 
676         if (readl(p) == 'E' + ('I'<<8) + ('S'<<16) + ('A'<<24))
677                 EISA_bus = 1;
678         early_iounmap(p, 4);
679 #endif
680 
681         set_intr_gate(0, ÷_error);
682         set_intr_gate_ist(2, &nmi, NMI_STACK);
683         /* int4 can be called from all */
684         set_system_intr_gate(4, &overflow);
685         set_intr_gate(5, &bounds);
686         set_intr_gate(6, &invalid_op);
687         set_intr_gate(7, &device_not_available);
688 #ifdef CONFIG_X86_32
689         set_task_gate(8, GDT_ENTRY_DOUBLEFAULT_TSS);
690 #else
691         set_intr_gate_ist(8, &double_fault, DOUBLEFAULT_STACK);
692 #endif
693         set_intr_gate(9, &coprocessor_segment_overrun);
694         set_intr_gate(10, &invalid_TSS);
695         set_intr_gate(11, &segment_not_present);
696         set_intr_gate_ist(12, &stack_segment, STACKFAULT_STACK);
697         set_intr_gate(13, &general_protection);
698         set_intr_gate(15, &spurious_interrupt_bug);
699         set_intr_gate(16, &coprocessor_error);
700         set_intr_gate(17, &alignment_check);
701 #ifdef CONFIG_X86_MCE
702         set_intr_gate_ist(18, &machine_check, MCE_STACK);
703 #endif
704         set_intr_gate(19, &simd_coprocessor_error);
705 
706         /* Reserve all the builtin and the syscall vector: */
707         for (i = 0; i < FIRST_EXTERNAL_VECTOR; i++)
708                 set_bit(i, used_vectors);
709 
710 #ifdef CONFIG_IA32_EMULATION
711         set_system_intr_gate(IA32_SYSCALL_VECTOR, ia32_syscall);
712         set_bit(IA32_SYSCALL_VECTOR, used_vectors);
713 #endif
714 
715 #ifdef CONFIG_X86_32
716         set_system_trap_gate(SYSCALL_VECTOR, &system_call);
717         set_bit(SYSCALL_VECTOR, used_vectors);
718 #endif
719 
720         /*
721          * Should be a barrier for any external CPU state:
722          */
723         cpu_init();
724 
725         x86_init.irqs.trap_init();
726 }

函數中先設置中斷向量表開頭的 17 個門,這些中斷向量都是 CPU 保留用於異常處理的。用於 debug 的 1 號門,int3 的 3 號門,和用於處理頁面異常的 14 號門不在這個函數裏初始化,而是在 early_trap_init() 中初始化的,這是爲了能夠方便於內核啓動早期的 debug 而設置的。early_trap_init() 是在 setup_arch() 中調用的。因此在 trap_init() 以後,系統的 20 個保留的中斷向量都已經設置好了。系統中有一個記錄哪些中斷向量被設置過的位圖 used_vectors,707-708 行就是用於設置這個位圖的相應位。其中,FIRST_EXTERNAL_VECTOR 定義在 arch/x86/include/asm/irq_vectors.h,值爲 0x20,也就是說,在 IDT 中,所有的外部中斷都是從 0x20 開始的,除了 0x80。716 行有個 SYSCALL_VECTOR,定義在 arch/x86/include/asm/irq_vectors.h,值爲 0x80,也就是系統調用 int 0x80 的入口。
x86_init 是一個 x86_init_ops 的結構體。x86_init_ops 定義了一些用於 x86 平臺的專用初始化函數。

/* filename: arch/x86/include/asm/x86_init.h */

132 struct x86_init_ops {
133         struct x86_init_resources       resources;
134         struct x86_init_mpparse         mpparse;
135         struct x86_init_irqs            irqs;
136         struct x86_init_oem             oem;
137         struct x86_init_mapping         mapping;
138         struct x86_init_paging          paging;
139         struct x86_init_timers          timers;
140         struct x86_init_iommu           iommu;
141         struct x86_init_pci             pci;
142 }; 

這個結構中,我們暫時只關心 irqs 成員,它是一個 x86_init_irqs 結構體,該結構體是用於定義 x86 平臺的一些和中斷相關的函數。

/* filename: arch/x86/include/asm/x86_init.h */

47 /**
48  * struct x86_init_irqs - platform specific interrupt setup
49  * @pre_vector_init:            init code to run before interrupt vectors
50  *                              are set up.
51  * @intr_init:                  interrupt init code
52  * @trap_init:                  platform specific trap setup
53  */
54 struct x86_init_irqs {
55         void (*pre_vector_init)(void);
56         void (*intr_init)(void);
57         void (*trap_init)(void);
58 };

在 x86_init 這個變量中,irqs.trap_init 被初始化爲 x86_init_noop。

/* filename: arch/x86/kernel/x86_init.c */

37 struct x86_init_ops x86_init __initdata = {
           ... ...         
55         .irqs = {
56                 .pre_vector_init        = init_ISA_irqs,
57                 .intr_init              = native_init_IRQ,
58                 .trap_init              = x86_init_noop,
59         },
           ... ...
91 };

而事實上,x86_init_noop() 是一個空函數,和 x86_init 定義在同一個 C 文件中。這樣的空函數應該是爲某些特殊的基於 x86 架構的平臺預留的,如果某些平臺需要保留自己的中斷向量,就需要去填充這些空函數。

現在回到 trap_init() 函數中,其中最重要的幾個函數是:set_intr_gate,set_system_intr_gate,set_intr_gate_ist 和 set_system_trap_gate。這幾個 set 系列的函數有相同之處,最終都是通過調用 _set_gate() 來完成設置 IDT 的。

/* filename: arch/x86/include/asm/desc.h */

310 static inline void _set_gate(int gate, unsigned type, void *addr,
311                              unsigned dpl, unsigned ist, unsigned seg)
312 {
313         gate_desc s;
314 
315         pack_gate(&s, type, (unsigned long)addr, dpl, ist, seg);
316         /*
317          * does not need to be atomic because it is only done once at
318          * setup time
319          */
320         write_idt_entry(idt_table, gate, &s);
321 }

329 static inline void set_intr_gate(unsigned int n, void *addr)
330 {               
331         BUG_ON((unsigned)n > 0xFF);
332         _set_gate(n, GATE_INTERRUPT, addr, 0, 0, __KERNEL_CS);
333 }

359 static inline void set_system_intr_gate(unsigned int n, void *addr)
360 {
361         BUG_ON((unsigned)n > 0xFF);
362         _set_gate(n, GATE_INTERRUPT, addr, 0x3, 0, __KERNEL_CS);
363 }
364 
365 static inline void set_system_trap_gate(unsigned int n, void *addr)
366 {
367         BUG_ON((unsigned)n > 0xFF);
368         _set_gate(n, GATE_TRAP, addr, 0x3, 0, __KERNEL_CS);
369 }

383 static inline void set_intr_gate_ist(int n, void *addr, unsigned ist)
384 {
385         BUG_ON((unsigned)n > 0xFF);
386         _set_gate(n, GATE_INTERRUPT, addr, 0, ist, __KERNEL_CS);
387 }

_set_gate() 有幾個參數,第一個 gate 描述的是 idt_table 中的第幾個入口項;第二個 type 是這個門的類型,中斷門 (110)、陷井門 (111)、調用門 (100)或者是任務門 (101);第三個 addr 是對應的中斷處理程序的入口地址;第四個 dpl 是 Descriptor Privilege Level,0 爲內核空間,3 爲用戶空間;第五個 ist 是 Interrupt Stack Table,共 3 位,表示 ist0 ~ ist7;第六個 seg,是內核段選擇子的值。在幾個 set 函數中,type 的值都被定義爲宏,這些宏定義在 arch/x86/include/asm/desc_defs.h 中,分別爲:GATE_INTERRUPT = 0xE,0xE = b110,表示中斷門;GATE_TRAP = 0xF,0xF = b111,表示陷井門。_set_gate() 中,先定義了一個 gate_desc 類型的變量 s,這個 s 是要寫入 idt_table 的門描述符的地址。gate_desc 是一個 desc_struct 的結構體:

/* filename: arch/x86/include/asm/desc_defs.h */

22 struct desc_struct {
23         union {
24                 struct {
25                         unsigned int a;
26                         unsigned int b;
27                 };
28                 struct {
29                         u16 limit0;
30                         u16 base0;
31                         unsigned base1: 8, type: 4, s: 1, dpl: 2, p: 1;
32                         unsigned limit: 4, avl: 1, l: 1, d: 1, g: 1, base2: 8;
33                 };
34         };
35 } __attribute__((packed));

在 315 行的 pack_gate() 函數中,只用到了聯合體的第一個元素,所以這裏只按照使用到的情況來介紹。聯合體的第一個元素用來表示一個 64 位的數據,其中 a 表示該地址的低 32 位;b 表示高 32 位。

/* filename: arch/x86/include/asm/desc.h */

68 static inline void pack_gate(gate_desc *gate, unsigned char type,
69                              unsigned long base, unsigned dpl, unsigned flags,
70                              unsigned short seg)
71 {
72         gate->a = (seg << 16) | (base & 0xffff);
73         gate->b = (base & 0xffff0000) | (((0x80 | type | (dpl << 5)) & 0xff) << 8);
74 } 

以 set_system_intr_gate() 爲例,這是對系統調用的門的設置,因爲系統調用是用戶程序調用的,因此係統調用發生在用戶空間,進而需要切換進入內核空間,所以系統調用的 dpl 值爲 0x3,這樣可以使得系統調用發生時,檢查權限等級的時候,用戶程序的權限等級不會比系統調用門的 dpl 等級低。另外,從之前的代碼中可以看到,seg 的值是 __KERNEL_CS,這是內核代碼段的地址,事實上,在保護模式下,__KERNEL_CS 是內核代碼段在 GDT 中的選擇子,其值爲 0x10。(base & 0xffff) 可以得到函數地址的低 16 位,假設爲 0xllll。那麼 72 行計算後得到的 gate->a 的值轉換爲二進制應該是 1 0000 llll llll llll llll。(base & 0xffff0000) 自然就得到函數地址的高 16 位,假設爲 0xhhhh,同時,(((0x80|type|(dpl<<5))&0xff)<<8) 的二進制結果爲 1110 0110 0000 0000。則 73 行計算後得到的 gate->b 的值轉換爲二進制應該是 hhhh hhhh hhhh hhhh 1110 0110 0000 0000。gate->b 和 gate->a 則是當前這個門描述符的高 32 位和低 32 位,組成了根據中斷門 (64 位) 和陷井門 (64 位) 門描述符的格式定義如下:


因此 pack_gate() 函數就是通過所提供的這些參數,構造出一箇中斷門描述符,並把這個中斷門描述符保存到臨時變量 s 中。接着 320 行調用 write_idt_entry() 把這個新產生的中斷門描述符寫入 idt_table 中的相應位置。

/* filename: arch/x86/include/asm/desc.h */

103 #define write_idt_entry(dt, entry, g)           native_write_idt_entry(dt, entry, g)

116 static inline void native_write_idt_entry(gate_desc *idt, int entry, const gate_desc *gate)
117 {
118         memcpy(&idt[entry], gate, sizeof(*gate));
119 }

write_idt_entry() 是一個宏定義,其原型爲 native_write_idt_entry()。320 行 write_idt_entry() 的第一個參數 idt_table 是一個全局的 gate_desc 類型的數組,定義在 arch/x86/kernel/traps.c 中,這個數組的大小爲 NR_VECTORS,即 256。native_write_idt_entry() 調用 memcpy() 將剛創建的 s 保存到 idt_table 的指定位置,這個指定位置是距 idt_table 基地址的一個偏移量,由 set_system_intr_gate() 中的第一個參數 n 給出。


當配置或沒配置 CONFIG_SPARSE_IRQ 時,early_irq_init() 函數實現形式不同。CONFIG_SPARSE_IRQ配置項,用於支持稀疏irq號,對於發行版的內核很有用,它允許定義一個高CONFIG_NR_CPUS值,但仍然不希望消耗太多內存的情況。如果配置了這個選項,那麼 irq_desc 數組就有一個稀疏的佈局,可以更普遍地來設置 irq_desc 數組的大小,一般是根據 CPU 的個數、IO-APIC 的個數來線性的擴展。而不配置該選項時,就必須小心的對待數組的大小,避免創建了過於大的靜態數組(?)。我們就按照不配置 CONFIG_SPARSE_IRQ 的情況接着往下看了:

/* filename: kernel/irq/irqdesc.c */

240 #else /* !CONFIG_SPARSE_IRQ */
241 
242 struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
243         [0 ... NR_IRQS-1] = {
244                 .handle_irq     = handle_bad_irq,
245                 .depth          = 1,
246                 .lock           = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
247         }
248 };
249 
250 int __init early_irq_init(void)
251 {
252         int count, i, node = first_online_node;
253         struct irq_desc *desc;
254 
255         init_irq_default_affinity();
256 
257         printk(KERN_INFO "NR_IRQS:%d\n", NR_IRQS);
258 
259         desc = irq_desc;
260         count = ARRAY_SIZE(irq_desc);
261 
262         for (i = 0; i < count; i++) {
263                 desc[i].kstat_irqs = alloc_percpu(unsigned int);
264                 alloc_masks(&desc[i], GFP_KERNEL, node);
265                 raw_spin_lock_init(&desc[i].lock);
266                 lockdep_set_class(&desc[i].lock, &irq_desc_lock_class);
267                 desc_set_defaults(i, &desc[i], node, NULL);
268         }
269         return arch_early_irq_init();
270 }

first_online_node???對於單處理器來說,init_irq_default_affinity() 爲空函數。接着打印出中斷個數的信息,中斷個數爲 NR_IRQS,它定義在 arch/x86/include/asm/irq_vectors.h 中,在沒有使用 IO_APIC 的情況下,NR_IRQS 的值等於 NR_IRQ_LEGACY,爲 16,這裏的 16 也就是兩片 8259A 級聯可以允許的最大中斷請求數。當然,如果使用了 APIC,中斷請求的數量肯定會遠遠大於 16,不過這裏還是按簡單的過程來討論。early_irq_init() 中定義了一個 irq_desc 結構的指針 desc,指向 242 行的 irq_desc 數組的開頭。irq_desc 是描述中斷的結構體,定義如下:

/* filename: include/linux/irqdesc.h */

15 /**
16  * struct irq_desc - interrupt descriptor
17  * @irq_data:           per irq and chip data passed down to chip functions
18  * @timer_rand_state:   pointer to timer rand state struct
19  * @kstat_irqs:         irq stats per cpu
20  * @handle_irq:         highlevel irq-events handler
21  * @preflow_handler:    handler called before the flow handler (currently used by sparc)
22  * @action:             the irq action chain
23  * @status:             status information
24  * @core_internal_state__do_not_mess_with_it: core internal status information
25  * @depth:              disable-depth, for nested irq_disable() calls
26  * @wake_depth:         enable depth, for multiple irq_set_irq_wake() callers
27  * @irq_count:          stats field to detect stalled irqs
28  * @last_unhandled:     aging timer for unhandled count
29  * @irqs_unhandled:     stats field for spurious unhandled interrupts
30  * @lock:               locking for SMP
31  * @affinity_hint:      hint to user space for preferred irq affinity
32  * @affinity_notify:    context for notification of affinity changes
33  * @pending_mask:       pending rebalanced interrupts
34  * @threads_oneshot:    bitfield to handle shared oneshot threads
35  * @threads_active:     number of irqaction threads currently running
36  * @wait_for_threads:   wait queue for sync_irq to wait for threaded handlers
37  * @dir:                /proc/irq/ procfs entry
38  * @name:               flow handler name for /proc/interrupts output
39  */
40 struct irq_desc {
41         struct irq_data         irq_data;
42         struct timer_rand_state *timer_rand_state;
43         unsigned int __percpu   *kstat_irqs;
44         irq_flow_handler_t      handle_irq;
45 #ifdef CONFIG_IRQ_PREFLOW_FASTEOI
46         irq_preflow_handler_t   preflow_handler;
47 #endif
48         struct irqaction        *action;        /* IRQ action list */
49         unsigned int            status_use_accessors;
50         unsigned int            core_internal_state__do_not_mess_with_it;
51         unsigned int            depth;          /* nested irq disables */
52         unsigned int            wake_depth;     /* nested wake enables */
53         unsigned int            irq_count;      /* For detecting broken IRQs */
54         unsigned long           last_unhandled; /* Aging timer for unhandled count */
55         unsigned int            irqs_unhandled;
56         raw_spinlock_t          lock;
57         struct cpumask          *percpu_enabled;
58 #ifdef CONFIG_SMP
59         const struct cpumask    *affinity_hint;
60         struct irq_affinity_notify *affinity_notify;
61 #ifdef CONFIG_GENERIC_PENDING_IRQ
62         cpumask_var_t           pending_mask;
63 #endif
64 #endif
65         unsigned long           threads_oneshot;
66         atomic_t                threads_active;
67         wait_queue_head_t       wait_for_threads;
68 #ifdef CONFIG_PROC_FS
69         struct proc_dir_entry   *dir;
70 #endif
71         struct module           *owner;
72         const char              *name;
73 } ____cacheline_internodealigned_in_smp;

start_kernel() 中 551 行是 init_IRQ():

/* filename: arch/x86/kernel/irqinit.c */

119 void __init init_IRQ(void)
120 {
121         int i;
122 
123         /*
124          * We probably need a better place for this, but it works for
125          * now ...
126          */
127         x86_add_irq_domains();
128 
129         /*
130          * On cpu 0, Assign IRQ0_VECTOR..IRQ15_VECTOR's to IRQ 0..15.
131          * If these IRQ's are handled by legacy interrupt-controllers like PIC,
132          * then this configuration will likely be static after the boot. If
133          * these IRQ's are handled by more mordern controllers like IO-APIC,
134          * then this vector space can be freed and re-used dynamically as the
135          * irq's migrate etc.
136          */
137         for (i = 0; i < legacy_pic->nr_legacy_irqs; i++)
138                 per_cpu(vector_irq, 0)[IRQ0_VECTOR + i] = i;
139 
140         x86_init.irqs.intr_init();
141 }

對於不支持 IO_APIC 的內核來說,x86_add_irq_domains() 是一個空函數。137 行中,legacy_pic 是一個描述 8259A 芯片的結構體,主要定義了一些操作:

/* filename: arch/x86/include/asm/i8259.h */

55 struct legacy_pic {
56         int nr_legacy_irqs;
57         struct irq_chip *chip;
58         void (*mask)(unsigned int irq);
59         void (*unmask)(unsigned int irq);
60         void (*mask_all)(void);
61         void (*restore_mask)(void);
62         void (*init)(int auto_eoi);
63         int (*irq_pending)(unsigned int irq);
64         void (*make_irq)(unsigned int irq);
65 };

legacy_pic 在 arch/x86/kernel/i8259.c 中被初始化爲 default_legacy_pic。其中,nr_cpu_ids 被初始化爲 NR_IRQS_LEGACY,即 16。per_cpu() 是一個宏定義,在非 SMP 的系統中:

/* filename: include/asm-generic/percpu.h */

77 #define VERIFY_PERCPU_PTR(__p) ({                       \
78         __verify_pcpu_ptr((__p));                       \
79         (typeof(*(__p)) __kernel __force *)(__p);       \
80 })
81 
82 #define per_cpu(var, cpu)       (*((void)(cpu), VERIFY_PERCPU_PTR(&(var))))

per_cpu(var, cpu) 獲取編號 cpu 的處理器上面的變量 var 的副本(參考:http://blog.csdn.net/fengtaocat/article/details/7078472)。138 行可以簡單的看作 vector_irq[IRQ0_VECTOR + i] = i;。vector_irq 是一個長度爲 NR_VECTORS (256) 的整形數組,每個數組元素的值都初始化爲 -1。IRQ0_VECTOR 定義在 arch/x86/include/asm/irq_vectors.h 中,其值爲 0x30:

/* filename: arch/x86/include/asm/irq_vectors.h */

54 /*
55  * Vectors 0x30-0x3f are used for ISA interrupts.
56  *   round up to the next 16-vector boundary
57  */     
58 #define IRQ0_VECTOR                     ((FIRST_EXTERNAL_VECTOR + 16) & ~15)
59 
60 #define IRQ1_VECTOR                     (IRQ0_VECTOR +  1)
61 #define IRQ2_VECTOR                     (IRQ0_VECTOR +  2)
62 #define IRQ3_VECTOR                     (IRQ0_VECTOR +  3)
63 #define IRQ4_VECTOR                     (IRQ0_VECTOR +  4)
64 #define IRQ5_VECTOR                     (IRQ0_VECTOR +  5)
65 #define IRQ6_VECTOR                     (IRQ0_VECTOR +  6)
66 #define IRQ7_VECTOR                     (IRQ0_VECTOR +  7)
67 #define IRQ8_VECTOR                     (IRQ0_VECTOR +  8)
68 #define IRQ9_VECTOR                     (IRQ0_VECTOR +  9)
69 #define IRQ10_VECTOR                    (IRQ0_VECTOR + 10)
70 #define IRQ11_VECTOR                    (IRQ0_VECTOR + 11)
71 #define IRQ12_VECTOR                    (IRQ0_VECTOR + 12)
72 #define IRQ13_VECTOR                    (IRQ0_VECTOR + 13)
73 #define IRQ14_VECTOR                    (IRQ0_VECTOR + 14)
74 #define IRQ15_VECTOR                    (IRQ0_VECTOR + 15)

因此,137 ~ 138 行的 for 循環就是把 vector_irq 數組中,第 0x30 ~ 0x3f 項的值設置爲 0 ~ 15。這是爲了???接着 140 行 intr_init() 是對中斷的初始化。intr_init() 函數的原型是 native_init_IRQ。

/* filename: arch/x86/kernel/irqinit.c */

295 void __init native_init_IRQ(void)
296 {
297         int i;
298 
299         /* Execute any quirks before the call gates are initialised: */
300         x86_init.irqs.pre_vector_init();
301 
302         apic_intr_init();
303 
304         /*
305          * Cover the whole vector space, no vector can escape
306          * us. (some of these will be overridden and become
307          * 'special' SMP interrupts)
308          */
309         for (i = FIRST_EXTERNAL_VECTOR; i < NR_VECTORS; i++) {
310                 /* IA32_SYSCALL_VECTOR could be used in trap_init already. */
311                 if (!test_bit(i, used_vectors))
312                         set_intr_gate(i, interrupt[i-FIRST_EXTERNAL_VECTOR]);
313         }
314 
315         if (!acpi_ioapic && !of_ioapic)
316                 setup_irq(2, &irq2);
317 
318 #ifdef CONFIG_X86_32
319         /*
320          * External FPU? Set up irq13 if so, for
321          * original braindamaged IBM FERR coupling.
322          */
323         if (boot_cpu_data.hard_math && !cpu_has_fpu)
324                 setup_irq(FPU_IRQ, &fpu_irq);
325 
326         irq_ctx_init(smp_processor_id());
327 #endif
328 }

在這些調用門初始化之前,需要先做一些預先的準備,即 pre_vector_init()。pre_vector_init() 的原型是 init_ISA_irqs():

/* filename: arch/x86/kernel/irqinit.c */

104 void __init init_ISA_irqs(void)
105 {
106         struct irq_chip *chip = legacy_pic->chip;
107         const char *name = chip->name;
108         int i;
109 
110 #if defined(CONFIG_X86_64) || defined(CONFIG_X86_LOCAL_APIC)
111         init_bsp_APIC();
112 #endif
113         legacy_pic->init(0);
114 
115         for (i = 0; i < legacy_pic->nr_legacy_irqs; i++)
116                 irq_set_chip_and_handler_name(i, chip, handle_level_irq, name);
117 }

106 和 107 行首先取得系統中 8259A 芯片的描述結構體和芯片的名字。legacy_pic 是一個描述傳統 8259A 芯片的結構體,被初始化爲 default_legacy_pic。

/* filename: arch/x86/kernel/i8259.c */

223 struct irq_chip i8259A_chip = {
224         .name           = "XT-PIC",
225         .irq_mask       = disable_8259A_irq,
226         .irq_disable    = disable_8259A_irq,
227         .irq_unmask     = enable_8259A_irq,
228         .irq_mask_ack   = mask_and_ack_8259A,
229 };

380 struct legacy_pic default_legacy_pic = {
381         .nr_legacy_irqs = NR_IRQS_LEGACY,
382         .chip  = &i8259A_chip,
383         .mask = mask_8259A_irq,
384         .unmask = unmask_8259A_irq,
385         .mask_all = mask_8259A,
386         .restore_mask = unmask_8259A,
387         .init = init_8259A,
388         .irq_pending = i8259A_irq_pending,
389         .make_irq = make_8259A_irq,
390 };
391         
392 struct legacy_pic *legacy_pic = &default_legacy_pic;

那麼這裏的 chip 和 name 則分別被初始化爲 i8259A_chip 和 "XT-PIC"。113 行 legacy_pic->init() 將初始化 8259A 芯片。

/* filename: arch/x86/kernel/i8259.c */

300 static void init_8259A(int auto_eoi)
301 {
302         unsigned long flags;
303         
304         i8259A_auto_eoi = auto_eoi;
305         
306         raw_spin_lock_irqsave(&i8259A_lock, flags);
307         
308         outb(0xff, PIC_MASTER_IMR);     /* mask all of 8259A-1 */
309         outb(0xff, PIC_SLAVE_IMR);      /* mask all of 8259A-2 */
310         
311         /*
312          * outb_pic - this has to work on a wide range of PC hardware.
313          */
314         outb_pic(0x11, PIC_MASTER_CMD); /* ICW1: select 8259A-1 init */
315         
316         /* ICW2: 8259A-1 IR0-7 mapped to 0x30-0x37 on x86-64,
317            to 0x20-0x27 on i386 */
318         outb_pic(IRQ0_VECTOR, PIC_MASTER_IMR);
319         
320         /* 8259A-1 (the master) has a slave on IR2 */
321         outb_pic(1U << PIC_CASCADE_IR, PIC_MASTER_IMR);
322 
323         if (auto_eoi)   /* master does Auto EOI */
324                 outb_pic(MASTER_ICW4_DEFAULT | PIC_ICW4_AEOI, PIC_MASTER_IMR);
325         else            /* master expects normal EOI */
326                 outb_pic(MASTER_ICW4_DEFAULT, PIC_MASTER_IMR);
327 
328         outb_pic(0x11, PIC_SLAVE_CMD);  /* ICW1: select 8259A-2 init */
329 
330         /* ICW2: 8259A-2 IR0-7 mapped to IRQ8_VECTOR */
331         outb_pic(IRQ8_VECTOR, PIC_SLAVE_IMR);
332         /* 8259A-2 is a slave on master's IR2 */
333         outb_pic(PIC_CASCADE_IR, PIC_SLAVE_IMR);
334         /* (slave's support for AEOI in flat mode is to be investigated) */
335         outb_pic(SLAVE_ICW4_DEFAULT, PIC_SLAVE_IMR);
336 
337         if (auto_eoi)
338                 /*
339                  * In AEOI mode we just have to mask the interrupt
340                  * when acking.
341                  */
342                 i8259A_chip.irq_mask_ack = disable_8259A_irq;
343         else
344                 i8259A_chip.irq_mask_ack = mask_and_ack_8259A;
345 
346         udelay(100);            /* wait for 8259A to initialize */
347 
348         outb(cached_master_mask, PIC_MASTER_IMR); /* restore master IRQ mask */
349         outb(cached_slave_mask, PIC_SLAVE_IMR);   /* restore slave IRQ mask */
350 
351         raw_spin_unlock_irqrestore(&i8259A_lock, flags);
352 }

/* filename: arch/x86/include/asm/i8259.h */

12 /* i8259A PIC registers */
13 #define PIC_MASTER_CMD          0x20
14 #define PIC_MASTER_IMR          0x21
15 #define PIC_MASTER_ISR          PIC_MASTER_CMD
16 #define PIC_MASTER_POLL         PIC_MASTER_ISR
17 #define PIC_MASTER_OCW3         PIC_MASTER_ISR
18 #define PIC_SLAVE_CMD           0xa0
19 #define PIC_SLAVE_IMR           0xa1
20 
21 /* i8259A PIC related value */
22 #define PIC_CASCADE_IR          2
23 #define MASTER_ICW4_DEFAULT     0x01
24 #define SLAVE_ICW4_DEFAULT      0x01
25 #define PIC_ICW4_AEOI           2

8259A 初始化期間是不允許使用的,所以要先加鎖。主片 8259A 的端口是 20H 和 21H,從片 8259A 的端口是 a0H 和 a1H。按照 8259A 的編程方式,先使用 OCW1 向兩個級聯的芯片寫屏蔽碼。OCW1 表示對該片 8259A 上相應中斷的屏蔽與否。308 和 309 兩行先屏蔽主從兩片 8259A 的所有 16 箇中斷請求端口。接着對芯片進行初始化操作,初始化中斷的過程是固定的。314 行使用 ICW1 向主片寫初始化命令 0x11,即 0001 0001,意爲 bit4 表示 ICW1 的特徵位;bit3 表示邊沿出發;bit1 表示多片級聯;bit0表示要寫 ICW4。318 行寫 ICW2,IRQ0_VECTOR (0x30,0011 0000),bit7 ~ bit3 表示中斷號的高 5 位;餘下的位爲 0。因爲有級聯,所以 321 行接着寫 ICW3,PIC_CASCADE_IR 值爲 2,則 321 行表示主片的 IR2 上有從片連接。最後 326 行寫 ICW4,MASTER_ICW4_DEFAULT 的值爲 0x1,bit4 表示爲一般嵌套;bit3 ~ bit2 表示爲非緩衝方式;bit1 表示爲自動結束中斷;bit0 表示爲 16 位系統。接着 328 ~ 335 初始化從片,和初始化主片的過程一樣。由於 ICW4 設置了自動結束中斷方式,所以中斷響應時,8259A 會自動將 ISR 對應位清除,所以需要將 8259A 描述結構體的 irq_mask_ack 設置爲 mask_and_ack_8259A 來屏蔽相應位的中斷嵌套。(參考《32 位微型計算機接口技術及應用》P137 ~ P144)。接下來的兩個變量 cached_master_mask 和 cached_slave_mask 的設置也很巧妙:

/* filename: arch/x86/include/asm/i8259.h */

 8 #define __byte(x, y)            (((unsigned char *)&(y))[x])
 9 #define cached_master_mask      (__byte(0, cached_irq_mask))
10 #define cached_slave_mask       (__byte(1, cached_irq_mask))

cached_irq_mask 是一個定義在 arch/x86/kernel/i8259.c 中的無符號整形數,初值爲 0xffff,其每一位代表了兩片 8259A 芯片的 16 箇中斷的使能狀態。在 __byte() 這個宏中,&(cached_irq_mask) 是一個無符號整形值的地址,現在把它強制類型轉換爲一個字符類型的地址,則 cached_irq_mask 就表示一個字符數組。因此,字符數組的第 0 個元素就是 cached_irq_mask 的低 8 位;而第 1 個元素就是高 8 位。這樣來表示兩片 8259A 中斷。最後把 cached_master_mask 和 cached_slave_mask 寫回,以使能相應的中斷。
init_ISA_irqs() 中 115 ~ 116 行爲每一個 IRQ 設置一個 IRQ 芯片和相應的處理函數,這裏的 handle_level_irq 被設置在對應的中斷描述符結構的 handle_irq 變量中。


當所有的設置都完成後,最後 local_irq_enable() 根據不同的架構,打開中斷控制器的中斷,在 x86 中使用 sti 指令來開啓中斷。

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