1. 中斷處理流程
當中斷發生時,Linux系統會跳轉到asm_do_IRQ()函數(所有中斷程序的總入口函數),並且把中斷號irq傳進來。根據中斷號,找到中斷號對應的irq_desc結構(irq_desc結構為內核中中斷的描述結構,內核中有一個irq_desc結構的數組irq_desc_ptrs[NR_IRQS]),然后調用irq_desc中的handle_irq函數,即中斷入口函數。我們編寫中斷的驅動,即填充並注冊irq_desc結構。
2. 中斷處理數據結構:irq_desc
Linux內核將所有的中斷統一編號,使用一個irq_desc[NR_IRQS]的結構體數組來描述這些中斷:每個數組項對應着一個中斷源(也可能是一組中斷源),記錄中斷入口函數、中斷標記,並提供了中斷的底層硬件訪問函數(中斷清除、屏蔽、使能)。另外通過這個結構體數組項中的action,能夠找到用戶注冊的中斷處理函數。
struct irq_desc { unsigned int irq; irq_flow_handler_t handle_irq; struct irq_chip *chip; struct msi_desc *msi_desc; void *handler_data; void *chip_data; struct irqaction *action; /* IRQ action list */ unsigned int status; /* IRQ status */ unsigned int depth; /* nested irq disables */ unsigned int wake_depth; /* nested wake enables */ unsigned int irq_count; /* For detecting broken IRQs */ unsigned long last_unhandled; /* Aging timer for unhandled count */ unsigned int irqs_unhandled; spinlock_t lock;
const char *name; } ____cacheline_internodealigned_in_smp;
(1)handle_irq:中斷的入口函數
(2)chip:包含這個中斷的清除、屏蔽、使能等底層函數
struct irq_chip { const char *name; 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 (*mask)(unsigned int irq); void (*mask_ack)(unsigned int irq); void (*unmask)(unsigned int irq); void (*eoi)(unsigned int irq); void (*end)(unsigned int irq); void (*set_affinity)(unsigned int irq, const struct cpumask *dest); int (*retrigger)(unsigned int irq); int (*set_type)(unsigned int irq, unsigned int flow_type); int (*set_wake)(unsigned int irq, unsigned int on); /* Currently used only by UML, might disappear one day.*/ #ifdef CONFIG_IRQ_RELEASE_METHOD void (*release)(unsigned int irq, void *dev_id); #endif /* * For compatibility, ->typename is copied into ->name. * Will disappear. */ const char *typename; };
(3)action:記錄用戶注冊的中斷處理函數、中斷標志等內容
struct irqaction { irq_handler_t handler; unsigned long flags; cpumask_t mask; const char *name; void *dev_id; struct irqaction *next; int irq; struct proc_dir_entry *dir; };
3. 中斷處理流程總結
(1) 發生中斷后,CPU執行異常向量vector_irq的代碼;
(2)在vector_irq里面,最終會調用中斷處理C程序總入口函數asm_do_IRQ();
(3)asm_do_IRQ()根據中斷號調用irq_des[NR_IRQS]數組中的對應數組項中的handle_irq();
(4)handle_irq()會使用chip的成員函數來設置硬件,例如清除中斷,禁止中斷,重新開啟中斷等;
(5)handle_irq逐個調用用戶在action鏈表中注冊的處理函數。
可見,中斷體系結構的初始化,就是構造irq_desc[NR_IRQS]這個數據結構;用戶注冊中斷就是構造action鏈表;用戶卸載中斷就是從action鏈表中去除對應的項。
4. Linux操作系統中斷初始化
(1)init_IRQ()函數用來初始化中斷體系結構,代碼位於arch/arm/kernel/irq.c
void __init init_IRQ(void) { int irq; for (irq = 0; irq < NR_IRQS; irq++) irq_desc[irq].status |= IRQ_NOREQUEST | IRQ_NOPROBE; #ifdef CONFIG_SMP bad_irq_desc.affinity = CPU_MASK_ALL; bad_irq_desc.cpu = smp_processor_id(); #endif init_arch_irq(); }
(2)init_arch_irq()函數,就是用來初始化irq_desc[NR_IRQS]的,與硬件平台緊密相關。init_arch_irq其實是一個函數指針,我們移植Linux內核時,以S3C2440平台為例,把init_arch_irq指向函數s3c24xx_init_irq()。
(3)s3c24xx_init_irq()函數在arch/arm/plat-s3c24xx/irq.c中定義,它為所有的中斷設置了芯片相關的數據結構irq_desc[irq].chip,設置了處理函數入口irq_desc[irq].handle_irq。
(4)以外部中斷EINT0為例:
for (irqno = IRQ_EINT0; irqno <= IRQ_EINT3; irqno++) { irqdbf("registering irq %d (ext int)\n", irqno); set_irq_chip(irqno, &s3c_irq_eint0t4); set_irq_handler(irqno, handle_edge_irq); set_irq_flags(irqno, IRQF_VALID); }
① set_irq_chip()的作用就是"irq_desc[irqno].chip = &s3c_irq_eint0t4",s3c_irq_eint0t4為系統提供了一套操作EINT0~EINT4的中斷底層函數集,內容如下
static struct irq_chip s3c_irq_eint0t4 = { .name = "s3c-ext0", .ack = s3c_irq_ack, .mask = s3c_irq_mask, .unmask = s3c_irq_unmask, .set_wake = s3c_irq_wake, .set_type = s3c_irqext_type, };
② set_irq_handler()函數的作用就是“irq_desc[irqno].handle_irq = handle_edge_irq”。發生中斷后,asm_do_IRQ()函數會調用中斷入口函數handle_edge_irq(),而handle_edge_irq()函數會調用用戶注冊的處理函數(即irq_desc[irqno].action)。
5. 用戶注冊中斷時帶來的中斷初始化
(1)用戶(驅動程序)通過request_irq()函數向內核注冊中斷處理函數,request_irq()函數根據中斷號找到數組irq_desc[irqno]對應的數組項,然后在它的action鏈表中添加一個action表項。該函數定義於:kernel/irq/manage.c,內容如下
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long irqflags, const char *devname, void *dev_id) { struct irqaction *action; struct irq_desc *desc; int retval; /* * handle_IRQ_event() always ignores IRQF_DISABLED except for * the _first_ irqaction (sigh). That can cause oopsing, but * the behavior is classified as "will not fix" so we need to * start nudging drivers away from using that idiom. */ if ((irqflags & (IRQF_SHARED|IRQF_DISABLED)) == (IRQF_SHARED|IRQF_DISABLED)) pr_warning("IRQ %d/%s: IRQF_DISABLED is not " "guaranteed on shared IRQs\n", irq, devname); #ifdef CONFIG_LOCKDEP /* * Lockdep wants atomic interrupt handlers: */ irqflags |= IRQF_DISABLED; #endif /* * Sanity-check: shared interrupts must pass in a real dev-ID, * otherwise we'll have trouble later trying to figure out * which interrupt is which (messes up the interrupt freeing * logic etc). */ if ((irqflags & IRQF_SHARED) && !dev_id) return -EINVAL; desc = irq_to_desc(irq); if (!desc) return -EINVAL; if (desc->status & IRQ_NOREQUEST) return -EINVAL; if (!handler) return -EINVAL; action = kmalloc(sizeof(struct irqaction), GFP_ATOMIC); if (!action) return -ENOMEM; action->handler = handler; action->flags = irqflags; cpus_clear(action->mask); action->name = devname; action->next = NULL; action->dev_id = dev_id; retval = __setup_irq(irq, desc, action); if (retval) kfree(action); #ifdef CONFIG_DEBUG_SHIRQ if (irqflags & IRQF_SHARED) { /* * It's a shared IRQ -- the driver ought to be prepared for it * to happen immediately, so let's make sure.... * We disable the irq to make sure that a 'real' IRQ doesn't * run in parallel with our fake. */ unsigned long flags; disable_irq(irq); local_irq_save(flags); handler(irq, dev_id); local_irq_restore(flags); enable_irq(irq); } #endif return retval; }
(2) request_irq()函數首先使用4個參數構造一個irqaction結構,然后調用__setup_irq函數將它鏈入鏈表中,簡要代碼如下:
static int __setup_irq(unsigned int irq, struct irqaction *new) { /* 判斷是否沒有注冊過,如果已經注冊了就判斷是否是可共享的中斷 */ p = &desc->action; old = *p; if (old) { if (!((old->flags & new->flags) & IRQF_SHARED) || ((old->flags ^ new->flags) & IRQF_TRIGGER_MASK)) { old_name = old->name; goto mismatch; } /* add new interrupt at end of irq queue */ do { p = &old->next; old = *p; } while (old); shared = 1; } /* 鏈入新表項 */ *p = new; /* 如果在鏈入之前不是空鏈,那么之前的共享中斷已經設置了中斷觸發方式,沒有必要重復設置 */ /* 如果鏈入之前是空鏈,那么就需要設置中斷觸發方式 */ if (!shared) { irq_chip_set_defaults(desc->chip); /* Setup the type (level, edge polarity) if configured: */ if (new->flags & IRQF_TRIGGER_MASK) { if (desc->chip && desc->chip->set_type) desc->chip->set_type(irq, new->flags & IRQF_TRIGGER_MASK); else printk(KERN_WARNING "No IRQF_TRIGGER set_type " "function for IRQ %d (%s)\n", irq, desc->chip ? desc->chip->name : "unknown"); } else compat_irq_chip_set_default_handler(desc); desc->status &= ~(IRQ_AUTODETECT | IRQ_WAITING | IRQ_INPROGRESS); if (!(desc->status & IRQ_NOAUTOEN)) { desc->depth = 0; desc->status &= ~IRQ_DISABLED; /* 啟動中斷 */ if (desc->chip->startup) desc->chip->startup(irq); else desc->chip->enable(irq); } else /* Undo nested disables: */ desc->depth = 1; } /* Reset broken irq detection when installing new handler */ desc->irq_count = 0; desc->irqs_unhandled = 0; new->irq = irq; register_irq_proc(irq); new->dir = NULL; register_handler_proc(irq, new); }
(3) __setup_irq()函數主要完成功能如下
① 將新建的irqaciton結構鏈入irq_desc[irq]結構體的action鏈表中
* 如果action鏈表為空,則直接鏈入
* 如果非空,則要判斷新建的irqaciton結構和鏈表中的irqaciton結構所表示的中斷類型是否一致:即是都聲明為“可共享的”,是否都是用相同的觸發方式,如果一致,則將新建的irqaciton結構鏈入
② 設置中斷的觸發方式;
③ 啟動中斷
6. 卸載中斷
卸載中斷使用函數free_irq()函數,該函數定義在kernel/irq/manage.c中,需要用到的兩個參數irq、dev_id。通過參數irq可以定位到action鏈表,再使用dev_id在鏈表中找到要卸載的表項(共享中斷的情況)。如果它是唯一表項,那么刪除中斷,還需要調用irq_desc[irq].chip->shutdown()或者irq_desc[irq].chip->disable()來關閉中斷
void free_irq(unsigned int irq, void *dev_id) { struct irq_desc *desc = irq_to_desc(irq); struct irqaction **p; unsigned long flags; WARN_ON(in_interrupt()); if (!desc) return; spin_lock_irqsave(&desc->lock, flags); p = &desc->action; for (;;) { struct irqaction *action = *p; if (action) { struct irqaction **pp = p; p = &action->next; if (action->dev_id != dev_id) continue; /* Found it - now remove it from the list of entries */ *pp = action->next; /* Currently used only by UML, might disappear one day.*/ #ifdef CONFIG_IRQ_RELEASE_METHOD if (desc->chip->release) desc->chip->release(irq, dev_id); #endif if (!desc->action) { desc->status |= IRQ_DISABLED; if (desc->chip->shutdown) desc->chip->shutdown(irq); else desc->chip->disable(irq); } spin_unlock_irqrestore(&desc->lock, flags); unregister_handler_proc(irq, action); /* Make sure it's not being used on another CPU */ synchronize_irq(irq); #ifdef CONFIG_DEBUG_SHIRQ /* * It's a shared IRQ -- the driver ought to be * prepared for it to happen even now it's * being freed, so let's make sure.... We do * this after actually deregistering it, to * make sure that a 'real' IRQ doesn't run in * parallel with our fake */ if (action->flags & IRQF_SHARED) { local_irq_save(flags); action->handler(irq, dev_id); local_irq_restore(flags); } #endif kfree(action); return; } printk(KERN_ERR "Trying to free already-free IRQ %d\n", irq); #ifdef CONFIG_DEBUG_SHIRQ dump_stack(); #endif spin_unlock_irqrestore(&desc->lock, flags); return; } }
7. Linux中斷處理流程分析
① 中斷總入口函數:asm_do_IRQ() (定義在:arch/arm/kernel/irq.c)
asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs) { struct pt_regs *old_regs = set_irq_regs(regs); irq_enter(); /* * Some hardware gives randomly wrong interrupts. Rather * than crashing, do something sensible. */ if (irq >= NR_IRQS) handle_bad_irq(irq, &bad_irq_desc); else generic_handle_irq(irq); /* AT91 specific workaround */ irq_finish(irq); irq_exit(); set_irq_regs(old_regs); }
② generic_handle_irq()會調用相應中斷號描述結構的handle_irq,等價於irq_desc[irq].handle_irq(irq, desc)
③ 普通中斷流程(以EINT0為例)
(1)irq_desc[IRQ_EINT0].handle_irq函數指針指向handle_edge_irq()(定義在:kernel/irq/chip.c),用來處理邊沿觸發的中斷,內容如下
void fastcall handle_edge_irq(unsigned int irq, struct irq_desc *desc) { kstat_cpu(cpu).irqs[irq]++; /* Start handling the irq */ desc->chip->ack(irq); /* Mark the IRQ currently in progress.*/ desc->status |= IRQ_INPROGRESS; action_ret = handle_IRQ_event(irq, action); }
(2)通過函數調用desc->chip->ack(irq)來響應中斷,實際上就是清除中斷以使得可以接受下一個中斷,有了之前數據結構初始化的前提了解,可以知道實際上執行的就是s3c_irq_eint0t4.ack函數
(3)handle_IRQ_event函數逐個執行action鏈表中用戶注冊的中斷處理函數,它在kernel/irq/handle.c中定義,關鍵代碼如下:
irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action) { do { ret = action->handler(irq, action->dev_id); if (ret == IRQ_HANDLED) status |= action->flags; retval |= ret; action = action->next; } while (action); }
(4)用戶通過函數request_irq()函數注冊中斷處理函數時候,傳入參數irq和dev_id,在這里這兩個參數被用戶注冊的中斷處理函數action->handler()所使用。可見用戶可以在注冊中斷處理函數的時候,指定參數dev_id,然后將來再由注冊的中斷處理函數使用這個參數。
④ 特殊處理流程(以外部中斷EINT5為例)
(1)在S3C2440處理器架構中,EINT5中斷屬於EINT4t7中斷集合,是一個子中斷。當EINT5中斷線發生中斷事件,那么將先跳轉到EINT4t7中斷號對應的中斷入口處理函數,也即是irq_desc[EINT4t7].handle_irq(irq,desc),進行具體子中斷確定,然后再跳轉到真正發生中斷的中斷入口處理函數執行。
(2)EINT5中斷注冊函數調用:
request_irq(IRQ_EINT5, eint5_irq, IRQT_BOTHEDGE, "S2", NULL);
其實我們在沒有注冊EINT5中斷源的時候,系統已經注冊了EINT4t7的中斷入口處理函數。中斷集合EINT4t7的中斷入口處理函數,是在arch/arm/plat-s3c24xx/irq.c中的函數s3c24xx_init_irq()來初始化的,內容如下:
set_irq_chained_handler(IRQ_EINT4t7, s3c_irq_demux_extint4t7);
(3)當發生EINT5中斷事件,匯編階段根據INTOFFSET確定中斷號為IRQ_EINT4t7,asm_do_IRQ函數通過傳入的這個參數,將跳轉到irq_desc[EINT4t7].handle_irq(irq,desc)函數執行,也就是函數s3c_irq_demux_extint4t7(irq, desc),該函數的主要內容如下:
static void s3c_irq_demux_extint4t7(unsigned int irq, struct irq_desc *desc) { unsigned long eintpnd = __raw_readl(S3C24XX_EINTPEND); unsigned long eintmsk = __raw_readl(S3C24XX_EINTMASK); eintpnd &= ~eintmsk; eintpnd &= 0xff; /* only lower irqs */ /* we may as well handle all the pending IRQs here */ while (eintpnd) { irq = __ffs(eintpnd); eintpnd &= ~(1<<irq); irq += (IRQ_EINT4 - 4); generic_handle_irq(irq); } }
(4)函數s3c_irq_demux_extint4t7()根據寄存器S3C24XX_EINTPEND、S3C24XX_EINTMASK重新計算中斷號,這個時候將計算出真正的中斷號IRQ_EINT5,然后通過generic_handle_irq(irq)來調用irq_desc[EINT5].handle_irq(irq,desc)。此后的過程與EINT0發生中斷后的執行過程類似。