Linux中斷子系統的初始化
注:以2.6.39內核源碼講解
Linux整個中斷處理體系其實可以分為兩個部分,一部分是系統完成的部分,另一部分是驅動工程師需要完成的部分(也就是我們用requst_irq注冊的處理函數),本次我們主要討論的是系統啟動的過程中對中斷子系統做了哪些事?
一、搬移異常向量表
內核在啟動的時候就是先運行start_kernel() , 然后她就會調用體系結構相關的setup_arch(&command_line), 如arm體系結構的在arch/arm/kernel/setup.c中, 進一步, 她就要初始化板級相關的設備,內核會在這個工程中調用early_trap_init()把異常向量表搬移到0xFFFF0000的位置。
void __init setup_arch(char **cmdline_p)
{
const struct machine_desc *mdesc;
setup_processor();
mdesc = setup_machine(machine_arch_type);
......
early_trap_init(); //把異常向量表搬移到0xFFFF0000的位置。
}
二、初始化中斷管理子系統
我們先來看一下Linux系統中,中斷管理系統的初始化。中斷系統的初始化主要由兩個函數來完成。在系統初始化的start_kernel()函數 (在文件init/main.c中定義)中可以看到:
asmlinkage void __init start_kernel(void)
{
……
setup_arch(&command_line);
trap_init();
……
early_irq_init();
init_IRQ();
……
}
Linux中斷機制的核心數據結構 irq_desc, 它完整地描述了一條中斷線 (或可簡單理解為 “一個中斷源” )。start_kernel()函數調用early_irq_init()和init_IRQ()兩個函數來初始化中斷管理系統。其實就是初始化irq_desc的結構。
1、early_irq_init()函數
注:這個函數在kernel/irq/irqdesc.c文件中定義。
在start_kernel()函數中調用了early_irq_init()函數,linux調用early_irq_init()初始化linux中斷系統的核心數據。early_irq_init屬于與硬件和平臺無關的通用邏輯層,為irq_desc[]中個元素的某些成員填充默認值,完成后調用體系相關的arch_early_irq_init函數完成進一步的初始化工作,不過ARM體系沒有實現arch_early_irq_init。
注:irq_desc[] 全局數組
struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
[0 ... NR_IRQS-1] = {
.handle_irq = handle_bad_irq,
.depth = 1,
.lock = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
}
};
int __init early_irq_init(void)
{
int count, i, node = first_online_node;
struct irq_desc *desc;
init_irq_default_affinity();
printk(KERN_INFO "NR_IRQS:%d\n", NR_IRQS);
desc = irq_desc;
count = ARRAY_SIZE(irq_desc);
for (i = 0; i < count; i++) {
desc[i].irq_data.irq = i;
desc[i].irq_data.chip = &no_irq_chip;
desc[i].kstat_irqs = alloc_percpu(unsigned int);
irq_settings_clr_and_set(desc, ~0, _IRQ_DEFAULT_INIT_FLAGS);
alloc_masks(desc + i, GFP_KERNEL, node);
desc_smp_init(desc + i, node);
lockdep_set_class(&desc[i].lock, &irq_desc_lock_class);
}
return arch_early_irq_init();
}
注: 在使用設備樹的高版本內核中,管理中斷描述符也可能是用基數樹去管理了,避免空間的浪費。
2、init_IRQ()函數
init_IRQ(void)函數是一個特定與體系結構的函數,對于ARM體系結構,在文件arch/arm/kernel/irq.c
void __init init_IRQ(void)
{
machine_desc->init_irq();
}
mdesc結構的init_irq成員,這是一個struct machine_desc類型的結構,mach_desc里定義了一些關鍵的體系架構相關的信息。
以mini2440為例:
其machine_desc結構的init_irq成員在文件arch/arm/mach-s3c2440/mach-mini2440.c中被賦值為s3c24xx_init_irq函數
MACHINE_START(MINI2440, "MINI2440")
/* Maintainer: Michel Pollet <buserror@gmail.com> */
.boot_params = S3C2410_SDRAM_PA + 0x100,
.map_io = mini2440_map_io,
.init_machine = mini2440_init,
.init_irq = s3c24xx_init_irq,
.timer = &s3c24xx_timer,
MACHINE_END
注:MACHINE_START宏的作用是對mach_desc結構體進行初始化。OK,終于找到了init_IRQ() 真正內容,在arch/arm/plat-s3c24xx/irq.c中定義:
/* s3c24xx_init_irq
*
* Initialise S3C2410 IRQ system
*/
3、init_IRQ()實例
對應平臺下面的代碼,和硬件平臺相關,為各個中斷源設置默認的處理函數
void __init s3c24xx_init_irq(void)
{
unsigned long pend;
unsigned long last;
int irqno;
int i;
for (i = 0; i < 4; i++) {
pend = __raw_readl(S3C24XX_EINTPEND);
if (pend == 0 || pend == last)
break;
__raw_writel(pend, S3C24XX_EINTPEND);
printk("irq: clearing pending ext status %08x\n", (int)pend);
last = pend;
}
.......
for (irqno = IRQ_EINT4t7; irqno <= IRQ_ADCPARENT; irqno++) {
/* set all the s3c2410 internal irqs */
switch (irqno) {
/* deal with the special IRQs (cascaded) */
case IRQ_EINT4t7:
case IRQ_EINT8t23:
case IRQ_UART0:
case IRQ_UART1:
case IRQ_UART2:
case IRQ_ADCPARENT:
irq_set_chip_and_handler(irqno, &s3c_irq_level_chip, handle_level_irq);
break;
case IRQ_RESERVED6:
case IRQ_RESERVED24:
/* no IRQ here */
break;
default:
//irqdbf("registering irq %d (s3c irq)\n", irqno);
irq_set_chip_and_handler(irqno, &s3c_irq_chip, handle_edge_irq);
set_irq_flags(irqno, IRQF_VALID);
}
}
/* 設置外部中斷的控制器(操作方法集),默認前級處理函數handle_irq */
for (irqno = IRQ_EINT0; irqno <= IRQ_EINT3; irqno++) {
irq_set_chip_and_handler(irqno, &s3c_irq_eint0t4, handle_edge_irq);
set_irq_flags(irqno, IRQF_VALID);
}
for (irqno = IRQ_EINT4; irqno <= IRQ_EINT23; irqno++) {
irq_set_chip_and_handler(irqno, &s3c_irqext_chip, handle_edge_irq);
set_irq_flags(irqno, IRQF_VALID);
}
......
}
這個函數完成對于中斷控制器的初始化,并且設置中斷描述符的相應的函數指針的值,以在中斷發生時發生時,調用這些函數來完成芯片級的處理。
irq_set_chip_and_handler()介紹:
1、可以設置中斷號對應的中斷描述符的chip,里面其實是一系列操作對于控制器的方法,以在中斷發生或者管理中斷時可以完成對于芯片的操作。
以IRQ_EINT4-IRQ_EINT23為例,它的chip是s3c_irqext_chip
static struct irq_chip s3c_irqext_chip = {
.name = "s3c-ext",
.irq_mask = s3c_irqext_mask,
.irq_unmask = s3c_irqext_unmask,
.irq_ack = s3c_irqext_ack,
.irq_set_type = s3c_irqext_type,
.irq_set_wake = s3c_irqext_wake
};
2、可以設置中斷發生的第一級處理函數比如 handle_level_irq 或者 handle_edge_irq,本質上就是設置irq_desc[irqno].handle_irq 的值。
其實中斷處理的時候一般分為兩級,第一級是調用irq_desc[irqno].handle_irq。它主要面向GIC的莫一中斷線IRQ line,跟當前中斷觸發電信號相關的一個函數,不同的中斷觸發方式,其中斷線處理的函數是不同的,不過主要是電平處理和邊沿處理兩種。 第二級才是我們驅動開發者自己注冊的中斷函數,也就是irq_desc[irqno].action->handler的調用。
上面這個函數就是為特定的中斷號設置好一個中斷處理例程,這里的例程可不是我們request_irq注冊的例程,Linux支持中斷共享,共享同一個中斷號的每一設備都可以有自己特定的中斷處理程序,用來描述這些中斷處理程序的結構會形成一個鏈表,這里設置的例程將會逐個調用特定中斷號上的各設備的中斷處理例程。