硬件   cpu在正常执行一条条指令的时候,可以由于某些原因跳到一些异常处理程序,执行一些   代码,然后再返回原来的程序运行。按照触发异常的源头,异常包括:
中断,异步的由外部器件发起,cpu响应后,保存现场,然后跳到特定位置执行,然后返回 中断处继续执行下面的程序
 
同步中断,由系统中一些错误的指令引起(比如除零,非法指令等),是和cpu同步的。 软中断(实现系统调用的地方)就是人为的通过指令来产生一个异常,然后cpu切换工作模式, 对应的进入内核空间开始执行代码
 
 
  中断系统的整个硬件包括:cpu,中断控制器,外设。在arm体系中这里的cpu指的是cpu核,   中断控制器一般是标准的gic,通常gic已经被Soc厂商集成在Soc中了, gic的输出接cpu核   上的irq和frq引脚, gic的输入接Soc内各ip模块的中断产生引脚, ip模块对应的中断线   (gic上的输入引脚)在Soc内已经定死。不清楚gic的输入引脚可否直接连Soc的io管脚
1 2 3   Soc pin |  in Soc:    \-->      -|--ip模块中断控制---->gic中断控制---->cpu中断控制                 |  
 
  一个中断从发起到cpu接收到的流程可以用上面的图来表示,需要配置各个中断控制中的   相关寄存器,使得中断在物理上被cpu接收到
中断实现    linux内核和中断相关的核心数据结构有:struct irq_desc (linux/irqdesc.h), 每个   中断号对应着一个这样的结构,所有的irq_desc以数组或是树的形式存在
1 2 3 4 5 6 7 8 9 10 11   struct irq_desc       |--> struct irq_data irq_data -----> |-> mask,irq,hwirq,state_use_accessors       |                                                           |-> struct irq_chip *chip       |                                                           |-> struct irq_domain *domain        |                                                           |-> void *handler_data, *chip_data...       |--> irq_flow_handler_t handle_irq          |--> struct irqaction *action ---->|-> irq_handler_t handler, thread_fn       |--> raw_spinlock_t lock           |-> void *dev_id, int irq, flags       |--> struct proc_dir_entry *dir    |-> unsigned long thread_flags, thread_mask       |--> const char *name...           |-> const char *name       |-> struct proc_dir_entry *dir... 
 
  对着上面的数据结构分析中断流程:(arm体系)
  中断被cpu接收到之后,汇编程序处理后调用的第一个程序是:   asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
1 2 3 4 5 6 7 8         -->handle_IRQ(irq, regs);              -->struct pt_regs *old_regs = set_irq_regs(regs);              irq_enter();              generic_handle_irq(irq);                   -->generic_handle_irq_desc(irq, desc);                              -->desc->handle_irq(irq, desc);      irq_exit();      set_irq_regs(old_regs); 
 
  最后调用的 handle_irq() 是注册在对应irq_desc中的的中断流函数, 处理中断嵌套等问题   一般电平中断用标准的: handle_level_irq() (kernel/irq/chip.c)       边沿中断用标准的: handle_edge_irq(), 上面的chip.c还有另外几种中断流函数,接着   中断流函数向下分析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14   handle_edge_irq(unsigned int irq, struct irq_desc *desc)       -->raw_spin_lock(&desc->lock);       ...      desc->irq_data.chip->irq_ack(&desc->irq_data);       ...     handle_irq_event(desc);      -->struct irqaction *action = desc->action;      raw_spin_unlock(&desc->lock);      handle_irq_event_percpu(desc, action);      -->...             action->handler(irq, action->dev_id);           ...      raw_spin_lock(&desc->lock);  raw_spin_unlock(&desc->lock); 
 
     最后调用的 handle()就是request_irq中注册的中断处理函数, 对应irq_desc中的action    中的handler。上面的中断流函数中调用了irq_ack(), irq_ack()是注册在irq_chip中的   函数, 最上面的数据结构显示irq_chip在irq_desc中的irq_data中
1 2 3 4 5 6 7 8 9 10 11   struct irq_chip {     ... void (*irq_enable)(struct irq_data *data); void (*irq_ack)(struct irq_data *data); void (*irq_mask)(struct irq_data *data); void (*irq_unmask)(struct irq_data *data); void (*irq_eoi)(struct irq_data *data); int (*irq_set_type)(struct irq_data *data, unsigned int flow_type);     ... unsigned long flags;   } 
 
  irq_chip包括和硬件相关的一组回调函数,他们直接操作中断相关的寄存器, 比如irq_enble   使能中断线,irq_mask屏蔽中断线
中断使用   在驱动程序中只需要调用request_irq()注册中断处理程序即可使用中断,上面的中断实现   为我们做了很多工作。注册中断和释放中断:
1 2 3 4 request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)     request_irq有可能会睡眠,里面会调用kmalloc()分配内存     free_irq(unsigned int irq, void *dev_id) 
 
  中断处理函数: irqreturn_t handler(int irq, void *dev_id) 无需要可重入,一个中   断在执行的时候,相同的中断在其他的所有的处理器上的中断线都会被屏蔽?
  request_irq()中使用中断号irq注册中断处理函数, 一个中断号也可以注册多个处理函数。   中断发生了,cpu就跳去执行中断处理函数,一个外设也可以发出几个不同的中断, 这时   可以在外设的驱动程序中注册多个中断处理函数
  当外设的中断引脚和gic的输入直接相连时,直接注册中断处理函数就可以使用中断。这是   因为gic的驱动程序(drivers/irqchip/irq-gic.c)已经实现了开头的数据结构和回调函数,   其中包括:gic对应的irq_chip中的回调函数(这些回调函数操作gic的输入,可以mask,   unmask, enable等等?), gic对应的中断流函数(gic_handle_irq()?)
  若是Soc内部的ip存在中断控制,比如gpio, 它一端接N个输入管脚,每个输入管脚可以接   收中断,它的中断输出接一个gic的输入,也就是说gpio的N个管脚上的中断输入,都反应   在gic的一个输入上。gpio中也有一组寄存器可以控制中断(enable, mask, unmask gpio   输入管脚上的中断)。这时我们要自己为这些中断建立开头的那写数据结构和回调函数
中断流函数   以一个gpio控制器的驱动程序为例说明,linux内核中各个厂商的gpio驱动在/drivers/gpio   内, 以gpio-mvebu.c来分析
  irq = platform_get_irq(pdev, i) 取出gpio对应的中断号,这个中断号是系统一开是就   定好的,是gic给gpio分配的中断号,这一个中断号可以对应gpio的多个输入。当gpio   引脚配置成中断引脚时,任何一个引脚上产生的中断都通过这个中断号上报给gic
1 2 3 4 5 6 7 8 9 10 11 12 13 irq_set_handler_data(irq, mvchip)    -->desc->irq_data.handler_data = data; irq_set_chained_handler(irq, mvebu_gpio_irq_handler)    -->__irq_set_handler(irq, handle, 1, NULL); // 1 表示:is_chained    desc->handled_irq = handle;           mvchip->irqbase = irq_alloc_descs(-1, 0, ngpios, -1) __irq_alloc_descs(irq, from, cnt, node, THIS_MODULE)    -->start=bitmap_find_next_zero_area(allocated_irqs,IRQ_BITMAP_BITS,from,cnt,0);             bitmap_set(allocated_irqs, start, cnt);    alloc_descs(start, cnt, node, owner);     以上分配了ngpios个连续的中断号,最后返回的是第一个中断号 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 gc = irq_alloc_generic_chip("mvebu_gpio_irq", 2, mvchip->irqbase,    mvchip->membase, handle_level_irq);     -->struct irq_chip_generic *gc;    unsigned long sz = sizeof(*gc) + num_ct * sizeof(struct irq_chip_type);    gc = kzalloc(sz, GFP_KERNEL);             irq_init_generic_chip(gc, name, num_ct, irq_base, reg_base, handler);         --> raw_spin_lock_init(&gc->lock);                 gc->num_ct = num_ct;                 gc->irq_base = irq_base;                 gc->reg_base = reg_base;                 gc->chip_types->chip.name = name;                 gc->chip_types->handler = handler; 
 
  以上动态分配了一个irq_chip_generic结构, 然后用之前分配的连续中断号中的第一个   中断号填充irq_base, 并且填充中断流函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16     stuct irq_chip_generic     |--> irq_base     |--> u32 mask_cache, tpye_cache     |--> void *private     |...     |--> struct irq_chip_type chip_types[0]--->|--> struct irq_chip     |                                                                         |--> irq_flow_handler_t handler                                                                               |--> type                                                                               |... gc->private = mvchip; // gc: *irq_chip_generic  ct = &gc->chip_types[0]; // ct: *irq_chip_type ct->type = IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_LEVEL_LOW; ct->chip.irq_mask = mvebu_gpio_level_irq_mask; ct->chip.irq_unmask = mvebu_gpio_level_irq_unmask; ct->chip.irq_set_type = mvebu_gpio_irq_set_type; ct->chip.name = mvchip->chip.label; 
 
  以上向irq_chip中注册了相应的回调函数,这些回调函数处理gpio中断相关,如mask,   ack, unmask gpio中断线。现在irq_desc结构也分配好了, irq_chip中的回调函数也注册   好了, 需要做的是向irq_desc中注册相应的irq_chip
1 2 3 4 5 irq_setup_generic_chip(gc, IRQ_MSK(ngpios), 0,       IRQ_NOREQUEST, IRQ_LEVEL | IRQ_NOPROBE);         -->list_add_tail(&gc->list, &gc_list); irq_set_chip_and_handler(i, chip, ct->handler); // for loop to do this irq_set_chip_data(i, gc); 
 
  以上先把上面动态生成的irq_chip_generic结构加入gc_list链表,然后向之前申请的   irq_desc注册各自的irq_chip和中断流函数
  mvchip->domain = irq_domain_add_simple();    添加一个irq_domain, 作用暂时不清楚