硬件 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, 作用暂时不清楚