1. 中断处理流程
当中断发生时,Linux系统会跳转到asm_do_IRQ()函数(所有中断程序的总入口函数),并且把终端号irq传进来。根据中断号,找到irq_desc结构(一个中断的描述结构),然后调用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”。发生中断后,do_asm_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()函数将它链入链表中,代码如下:
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; } }
原文地址:https://www.cnblogs.com/wulei0630/p/9502143.html