Linux中断流程分析

来源:互联网 发布:nba奥尼尔身高数据 编辑:程序博客网 时间:2024/06/05 18:09
<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">我们在认识系统处理中断流程时,先要知道在硬件上,外部设备和cpu和中断控制器是如何连接的。</span>




 当电平触发,或边沿触发一个外设产生一个硬件中断,这个电平信号会送到和这个外设相连的中断控制器,有可能中断控制器是级联的,
这个中断控制器会将中断信号送达上一级中断控制,最后由root中断控制器送到CPU,在这里又会遇到一个问题,一般系统不止一个CPU,
那么到底传给哪个CPU来处理呢,当然这里还有很多处理机制,还要结合gic的硬件结构,我也没有深入学习。

了解了中断硬件结构那么我们来看一下在高通代码中的软件流程
系统起来后,首先会初始化中断控制器,在高通8994代码中,有两个中断控制器
qcom,msm-qgic2 和qcom,msm-tlmm-gp,qcom,msm-tlmm-gp是第二级中断控制器,这个我们可以在device tree 中
grep -r "gpio-controller"
中断控制器在device tree 中都会有 gpio-controller 的标记。
而这些中断控制器会被编译进一个段中如:
IRQCHIP_DECLARE(msm_qgic2, "qcom,msm-qgic2", msm_gic_of_init);IRQCHIP_DECLARE(tlmmv3_irq, "qcom,msm-tlmm-gp", irq_msm_gpio_init);#define IRQCHIP_DECLARE(name,compstr,fn)\static const struct of_device_id irqchip_of_match_##name\__used __section(__irqchip_of_table)\= { .compatible = compstr, .data = fn }#ifdef CONFIG_IRQCHIP#define IRQCHIP_OF_MATCH_TABLE()\. = ALIGN(8);\VMLINUX_SYMBOL(__irqchip_begin) = .;\*(__irqchip_of_table)  \*(__irqchip_of_end)void __init irqchip_init(void){of_irq_init(__irqchip_begin);}
系统起来后会初始化各个模块会调用
asmlinkage void __init start_kernel(void){···init_IRQ();···}void __init init_IRQ(void){irqchip_init();if (!handle_arch_irq)panic("No interrupt controller found.");}void __init irqchip_init(void){of_irq_init(__irqchip_begin);}void __init of_irq_init(const struct of_device_id *matches) {····for_each_matching_node(np, matches) {      // 由于传入的np 为空,所以会遍历这个device treeif (!of_find_property(np, "interrupt-controller", NULL))  //找到包含"interrupt-controller"属性的节点continue; desc = kzalloc(sizeof(*desc), GFP_KERNEL);if (WARN_ON(!desc))goto err;desc->dev = np;desc->interrupt_parent = of_irq_find_parent(np); //查找这个节点有没有父设备if (desc->interrupt_parent == np)desc->interrupt_parent = NULL;list_add_tail(&desc->list, &intc_desc_list);   //将找到的中断控制器信息添加到链表中}····while (!list_empty(&intc_desc_list)) {list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list) {  //遍历保存中断控制器信息的链表const struct of_device_id *match;int ret;of_irq_init_cb_t irq_init_cb;if (desc->interrupt_parent != parent)  //由于在此parent为空,意思就是找到最顶层的GICcontinue;list_del(&desc->list);match = of_match_node(matches, desc->dev);if (WARN(!match->data,    "of_irq_init: no init function for %s\n",    match->compatible)) {kfree(desc);continue;}pr_debug("of_irq_init: init %s @ %p, parent %p\n", match->compatible, desc->dev, desc->interrupt_parent);irq_init_cb = (of_irq_init_cb_t)match->data;   //在此处,就把我们编译到段中的数据赋值给变量ret = irq_init_cb(desc->dev, desc->interrupt_parent); //这里就调到了GIC的初始化函数了if (ret) {kfree(desc);continue;}list_add_tail(&desc->list, &intc_parent_list);}desc = list_first_entry(&intc_parent_list, typeof(*desc), list);if (list_empty(&intc_parent_list) || !desc) {pr_err("of_irq_init: children remain, but no parents\n");break;}list_del(&desc->list);parent = desc->dev;     //将最顶层的GIC 赋值给parent,接下来的步骤会依次执行它的子GIC的初始化代码kfree(desc);}}
接下来我们就来看一下中断控制器的初始化。
在看gic的代码之前要知道什么是 irq_domain irq_number与HW interrupt ID,我这里就不再讲解,我这里是主要讲软件的流程。
int __init gic_of_init(struct device_node *node, struct device_node *parent){···dist_base = of_iomap(node, 0);    //GIC Distributor的地址WARN(!dist_base, "unable to map gic dist registers\n");cpu_base = of_iomap(node, 1);     //GIC CPU interface的地址 WARN(!cpu_base, "unable to map gic cpu registers\n");if (of_property_read_u32(node, "cpu-offset", &percpu_offset))percpu_offset = 0;gic_init_bases(gic_cnt, -1, dist_base, cpu_base, percpu_offset, node); ···if (parent) {//有可能这个GIC是级联的GIC不是最顶层的GIC,它还是要注册它自己的中断irq = irq_of_parse_and_map(node, 0);gic_cascade_irq(gic_cnt, irq);}}void __init gic_init_bases(unsigned int gic_nr, int irq_start,   void __iomem *dist_base, void __iomem *cpu_base,   u32 percpu_offset, struct device_node *node){···gic_irqs = readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_CTR) & 0x1f;  //读取寄存器中的值gic_irqs = (gic_irqs + 1) * 32;  //计算出此GIC能支持多少个中断if (gic_irqs > 1020)       gic_irqs = 1020;      //最多支持1020个中断gic_irqs -= hwirq_base; //减去16个,0-15的中断号是特殊的中断号irq_base = irq_alloc_descs(irq_start, 16, gic_irqs, numa_node_id());  //开始分配中断号,从16号开始分配if (IS_ERR_VALUE(irq_base)) {WARN(1, "Cannot allocate irq_descs @ IRQ%d, assuming pre-allocated\n",     irq_start);irq_base = irq_start;}gic->domain = irq_domain_add_legacy(node, gic_irqs, irq_base,          //将HW interrupt id和irq number 映射起来    hwirq_base, &gic_irq_domain_ops, gic);···}#define irq_alloc_descs(irq, from, cnt, node)\__irq_alloc_descs(irq, from, cnt, node, THIS_MODULE)int __ref __irq_alloc_descs(int irq, unsigned int from, unsigned int cnt, int node,  struct module *owner){···start = bitmap_find_next_zero_area(allocated_irqs, IRQ_BITMAP_BITS,  //从from开始,在位图中找到第一个不为0的区域   from, cnt, 0);   ret = -EEXIST;if (irq >=0 && start != irq)goto err;if (start + cnt > nr_irqs) {ret = irq_expand_nr_irqs(start + cnt);if (ret)goto err;}bitmap_set(allocated_irqs, start, cnt);  //将分配了的位,置位,也就是在allocated_irqs中,每一位对应了一个中断。mutex_unlock(&sparse_irq_lock);return alloc_descs(start, cnt, node, owner); //分配中断号,从start开始,一共分配cnt个···}static int alloc_descs(unsigned int start, unsigned int cnt, int node,       struct module *owner){struct irq_desc *desc;      //中断描述符,每一个中断号,对应了一个中断描述符int i;for (i = 0; i < cnt; i++) {desc = alloc_desc(start + i, node, owner);  //依次分配中断描述符if (!desc)goto err;mutex_lock(&sparse_irq_lock);irq_insert_desc(start + i, desc); //将中断号与中断描述符建立关系,这里使用的是基数树,方便在以后,  //只要知道了中断号,就可以得到对应的中断描述符mutex_unlock(&sparse_irq_lock);}return start;···}static struct irq_desc *alloc_desc(int irq, int node, struct module *owner){···desc_set_defaults(irq, desc, node, owner); //将中断描述符赋值,这个只是一些缺省值,后面还会赋值···}

分配中断号就分析到这里,我们再回到gic_init_bases函数中。
现在已经为gic支持的所有中断,分配了中断号,并且每一个中断号都对应有一个中断描述符,接下来就要将硬件id和irq number
映射起来,当中断产生时,CPU会读GIC的寄存器,知道是哪个HW interrupt id,再通过这种映射好的关系,就能得到irq number
再通过irq number得到对象的中断描述符,然后执行handler 再执行注册的 action。


对应HW interrupt id 和 irq number的映射,可以有很多种,我们这里使用的是 IRQ_DOMAIN_MAP_LEGACY
#define IRQ_DOMAIN_MAP_LEGACY 0 /* driver allocated fixed range of irqs.
* ie. legacy 8259, gets irqs 1..15 */
#define IRQ_DOMAIN_MAP_NOMAP 1 /* no fast reverse mapping */
#define IRQ_DOMAIN_MAP_LINEAR 2 /* linear map of interrupts */
#define IRQ_DOMAIN_MAP_TREE 3 /* radix tree */

struct irq_domain *irq_domain_add_legacy(struct device_node *of_node, unsigned int size, unsigned int first_irq, irq_hw_number_t first_hwirq, const struct irq_domain_ops *ops, void *host_data){struct irq_domain *domain;unsigned int i;domain = irq_domain_alloc(of_node, IRQ_DOMAIN_MAP_LEGACY, ops, host_data);  //分配irq_domain if (!domain)return NULL;//其实这种映射类似于线性映射first_irq 对应 first_hwirq,first_irq+i对应first_hwirq+i//当知道一个hw interrupt id时 hw_interrupt_id - first_hwirq + first_irq 就得到irq_number了domain->revmap_data.legacy.first_irq = first_irq; domain->revmap_data.legacy.first_hwirq = first_hwirq;domain->revmap_data.legacy.size = size;mutex_lock(&irq_domain_mutex);/* Verify that all the irqs are available */  for (i = 0; i < size; i++) {int irq = first_irq + i;struct irq_data *irq_data = irq_get_irq_data(irq);if (WARN_ON(!irq_data || irq_data->domain)) {mutex_unlock(&irq_domain_mutex);irq_domain_free(domain);return NULL;}}/* Claim all of the irqs before registering a legacy domain */for (i = 0; i < size; i++) {struct irq_data *irq_data = irq_get_irq_data(first_irq + i);irq_data->hwirq = first_hwirq + i;   //将相应的属性赋值irq_data->domain = domain;}mutex_unlock(&irq_domain_mutex);for (i = 0; i < size; i++) {int irq = first_irq + i;int hwirq = first_hwirq + i;/* IRQ0 gets ignored */if (!irq)continue;/* Legacy flags are left to default at this point, * one can then use irq_create_mapping() to * explicitly change them */if (ops->map)ops->map(domain, irq, hwirq);   //这是一个回调函数,在gic_init_bases传进来的函数,主要是设置chip和handler/* Clear norequest flags */irq_clear_status_flags(irq, IRQ_NOREQUEST);}irq_domain_add(domain);     //将注册好的domain 添加到系统的irq_domain_list中return domain;}
传入的映射函数:
static int gic_irq_domain_map(struct irq_domain *d, unsigned int irq,irq_hw_number_t hw){if (hw < 32) {             //特殊的中断irq_set_percpu_devid(irq);irq_set_chip_and_handler(irq, &gic_chip, handle_percpu_devid_irq);set_irq_flags(irq, IRQF_VALID | IRQF_NOAUTOEN);} else {irq_set_chip_and_handler(irq, &gic_chip,      //设置chip 和 handler       //chip: gic_chip      //handler:handle_fasteoi_irq handle_fasteoi_irq);set_irq_flags(irq, IRQF_VALID | IRQF_PROBE);}irq_set_chip_data(irq, d->host_data);  //这里的host_data 是gic私有的,我们不能去修改它return 0;}
gic_chip 主要是一些操作GIC硬件的接口
static struct irq_chip gic_chip = {.name= "GIC",.irq_mask= gic_mask_irq,.irq_unmask= gic_unmask_irq,.irq_eoi= gic_eoi_irq,.irq_set_type= gic_set_type,.irq_retrigger= gic_retrigger,#ifdef CONFIG_SMP.irq_set_affinity= gic_set_affinity,#endif.irq_disable= gic_disable_irq,.irq_set_wake= gic_set_wake,};

handler 也就是 highlevel irq-events handler 

我们这里的gic的highlevel handler用的是handle_fasteoi_irq
还有很多high level handler 比如:
handle_edge_irq
handle_level_irq
handle_nested_irq
···
至此gic的初始化基本完成,这里只介绍了一个大致的流程。
接下来就是申请中断。
内核的申请函数:

int request_threaded_irq(unsigned int irq, irq_handler_t handler, irq_handler_t thread_fn, unsigned long irqflags, const char *devname, void *dev_id){struct irqaction *action;struct irq_desc *desc;int retval;/* * 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)  //如果中断是共享的,但是没有dev_id那么直接返回错误return -EINVAL;desc = irq_to_desc(irq);   //根据传入的要申请的中断号,找到此中断对应的中断描述符if (!desc)return -EINVAL;if (!irq_settings_can_request(desc) ||          //有些中断号是不能被申请的,比如用于级联的中断号    WARN_ON(irq_settings_is_per_cpu_devid(desc))) //这个是针对percpu的另外一种情况,需要调用request_percpu_irq接口return -EINVAL;                           //有兴趣可以学习一下if (!handler) {if (!thread_fn)   //如果handler 和thread_fn都为空,是不行的。return -EINVAL;handler = irq_default_primary_handler;     //这里设置缺省值,其实是唤醒我们的thread_fn}action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);if (!action)return -ENOMEM;//以下为将action 赋值action->handler = handler;action->thread_fn = thread_fn;action->flags = irqflags;action->name = devname;action->dev_id = dev_id;chip_bus_lock(desc);retval = __setup_irq(irq, desc, action);   //进行实际的注册chip_bus_sync_unlock(desc);if (retval)kfree(action);#ifdef CONFIG_DEBUG_SHIRQ_FIXMEif (!retval && (irqflags & IRQF_SHARED)) {unsigned long flags;disable_irq(irq);local_irq_save(flags);handler(irq, dev_id);local_irq_restore(flags);enable_irq(irq);}#endifreturn retval;}
再来看一下实际的注册:

static int __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new){struct irqaction *old, **old_ptr;unsigned long flags, thread_mask = 0;int ret, nested, shared = 0;cpumask_var_t mask;if (!desc)return -EINVAL;if (desc->irq_data.chip == &no_irq_chip)return -ENOSYS;if (!try_module_get(desc->owner))return -ENODEV;nested = irq_settings_is_nested_thread(desc);  //判断此irq是不是嵌套中断,如果是嵌套的,那么它的执行是依赖于父中断的thread_fnif (nested) {if (!new->thread_fn) {ret = -EINVAL;goto out_mput;}new->handler = irq_nested_primary_handler;  //打印调试信息,正常流程时不会调用的。} else {if (irq_settings_can_thread(desc))  //强制线程化,在我们的代码中,这个宏是没有打开的。irq_setup_forced_threading(new);}if (new->thread_fn && !nested) { //如果thread_fn不为空,并且没有嵌套,那么内核将创建一个线程。struct task_struct *t;t = kthread_create(irq_thread, new, "irq/%d-%s", irq,   new->name);if (IS_ERR(t)) {ret = PTR_ERR(t);goto out_mput;}get_task_struct(t);  //为这个threaded handler的task struct增加一次reference count,这样,即便是     //该thread异常退出也可以保证它的task struct不会被释放掉new->thread = t;set_bit(IRQTF_AFFINITY, &new->thread_flags);}if (!alloc_cpumask_var(&mask, GFP_KERNEL)) {ret = -ENOMEM;goto out_thread;}if (desc->irq_data.chip->flags & IRQCHIP_ONESHOT_SAFE)new->flags &= ~IRQF_ONESHOT;raw_spin_lock_irqsave(&desc->lock, flags);//一下是中断共享的代码old_ptr = &desc->action;old = *old_ptr;if (old) {if (!((old->flags & new->flags) & IRQF_SHARED) ||         //如果是中断共享,那么此中断要和之前的中断的特性一样    ((old->flags ^ new->flags) & IRQF_TRIGGER_MASK) ||    ((old->flags ^ new->flags) & IRQF_ONESHOT))goto mismatch;/* All handlers must agree on per-cpuness */if ((old->flags & IRQF_PERCPU) !=    (new->flags & IRQF_PERCPU))goto mismatch;/* 将新的action加到队列的最后 */do {thread_mask |= old->thread_mask;old_ptr = &old->next;old = *old_ptr;} while (old);shared = 1;}if (new->flags & IRQF_ONESHOT) {if (thread_mask == ~0UL) {  //如果thread_mask 所有位都为1,表示这个中断号上已经挂了太多的共享中断了ret = -EBUSY;goto out_mask;}new->thread_mask = 1 << ffz(thread_mask); //找到为0的位,然后置1,其实就是thread_mask的每一位都代表着一个共享中断} else if (new->handler == irq_default_primary_handler &&  //这里有一种情况,就是当中断没有oneshot标记,并且是电平触发,   //如果底层的irq chip 也不是oneshot,那就有可能出现,一直触发中断的情况,   // 因为这里没有清除中断位的操作   //    !(desc->irq_data.chip->flags & IRQCHIP_ONESHOT_SAFE)) {pr_err("Threaded irq requested with handler=NULL and !ONESHOT for irq %d\n",       irq);ret = -EINVAL;goto out_mask;}if (!shared) {init_waitqueue_head(&desc->wait_for_threads);if (new->flags & IRQF_TRIGGER_MASK) {ret = __irq_set_trigger(desc, irq,        //这里就是设置中断的触发方式。new->flags & IRQF_TRIGGER_MASK);if (ret)goto out_mask;}desc->istate &= ~(IRQS_AUTODETECT | IRQS_SPURIOUS_DISABLED | \  IRQS_ONESHOT | IRQS_WAITING);irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);if (new->flags & IRQF_PERCPU) {irqd_set(&desc->irq_data, IRQD_PER_CPU);irq_settings_set_per_cpu(desc);}if (new->flags & IRQF_ONESHOT)desc->istate |= IRQS_ONESHOT;if (irq_settings_can_autoenable(desc))irq_startup(desc, true);else/* Undo nested disables: */desc->depth = 1;/* Exclude IRQ from balancing if requested */if (new->flags & IRQF_NOBALANCING) {irq_settings_set_no_balancing(desc);irqd_set(&desc->irq_data, IRQD_NO_BALANCING);}/* Set default affinity mask once everything is setup */setup_affinity(irq, desc, mask);} else if (new->flags & IRQF_TRIGGER_MASK) {unsigned int nmsk = new->flags & IRQF_TRIGGER_MASK;unsigned int omsk = irq_settings_get_trigger_mask(desc);if (nmsk != omsk)/* hope the handler works with current  trigger mode */pr_warning("irq %d uses trigger mode %u; requested %u\n",   irq, nmsk, omsk);}new->irq = irq;*old_ptr = new;/* Reset broken irq detection when installing new handler */desc->irq_count = 0;desc->irqs_unhandled = 0;if (shared && (desc->istate & IRQS_SPURIOUS_DISABLED)) {desc->istate &= ~IRQS_SPURIOUS_DISABLED;__enable_irq(desc, irq, false);}raw_spin_unlock_irqrestore(&desc->lock, flags);if (new->thread)wake_up_process(new->thread);register_irq_proc(irq, desc);new->dir = NULL;register_handler_proc(irq, new);free_cpumask_var(mask);return 0;···}
整个中断的申请到已完成,接下来就等外部来触发它了。
我这里用gic的一个中断来大致看一下处理流程:
当一个连到GIC上的中断产生时,系统会进入IRQ mode,会找异常向量表,最后会调到函数
handle_arch_irq

我们在gic的初始化代码中看以看到
set_handle_irq(gic_handle_irq);

所以当会调到 gic_handle_irq

static asmlinkage void __exception_irq_entry gic_handle_irq(struct pt_regs *regs){u32 irqstat, irqnr;struct gic_chip_data *gic = &gic_data[0];void __iomem *cpu_base = gic_data_cpu_base(gic);do {irqstat = readl_relaxed_no_log(cpu_base + GIC_CPU_INTACK); //读取gic的寄存器,可以得到是哪个硬件中断产生的中断irqnr = irqstat & ~0x1c00;if (likely(irqnr > 15 && irqnr < 1021)) { irqnr = irq_find_mapping(gic->domain, irqnr); //以hwid 从irq domain中找到相应的irq numberhandle_IRQ(irqnr, regs);  uncached_logk(LOGK_IRQ, (void *)(uintptr_t)irqnr);continue;}if (irqnr < 16) {   //特殊的中断     //ID0~ID15用于SGI,ID16~ID31用于PPI。    //PPI类型的中断会送到指定的process上,和其他的process无关。    //SGI是通过写GICD_SGIR寄存器而触发的中断writel_relaxed_no_log(irqstat, cpu_base + GIC_CPU_EOI);#ifdef CONFIG_SMPhandle_IPI(irqnr, regs);#endifuncached_logk(LOGK_IRQ, (void *)(uintptr_t)irqnr);continue;}break;} while (1);}void handle_IRQ(unsigned int irq, struct pt_regs *regs){···generic_handle_irq(irq);···}int generic_handle_irq(unsigned int irq){struct irq_desc *desc = irq_to_desc(irq); //通过irq number得到对应的中断描述符if (!desc)return -EINVAL;generic_handle_irq_desc(irq, desc); //将中断描述符和中断号传入,进一步处理return 0;}static inline void generic_handle_irq_desc(unsigned int irq, struct irq_desc *desc){desc->handle_irq(irq, desc);  //调用我们这个中断描述符中的handler      //还记得我们在gic初始化的时候,有一个map函数吗?里面的操作就是将handler_irq 赋值      //irq_set_chip_and_handler(irq, &gic_chip, handle_fasteoi_irq);}void handle_fasteoi_irq(unsigned int irq, struct irq_desc *desc){raw_spin_lock(&desc->lock); //获得锁if (unlikely(irqd_irq_inprogress(&desc->irq_data))) //如果此中断正在被其它的CPU处理,并且没有被轮询,那么直接退出。if (!irq_check_poll(desc))goto out;desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);kstat_incr_irqs_this_cpu(irq, desc);if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data))) {if (!irq_settings_is_level(desc))desc->istate |= IRQS_PENDING;mask_irq(desc);goto out;}if (desc->istate & IRQS_ONESHOT)  //如果是oneshot 那么立即mask住。mask_irq(desc);preflow_handler(desc);handle_irq_event(desc);if (desc->istate & IRQS_ONESHOT)cond_unmask_irq(desc);···}irqreturn_t handle_irq_event(struct irq_desc *desc){struct irqaction *action = desc->action;irqreturn_t ret;desc->istate &= ~IRQS_PENDING;  //清除PENGDING状态irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS); //设置正杂处理的状态raw_spin_unlock(&desc->lock);   //在执行具体action时,是将锁释放了的。ret = handle_irq_event_percpu(desc, action);   //遍历action列表,依次执行raw_spin_lock(&desc->lock);irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);return ret;}

到此,整个中断的流程就结束了,其中还有很多细节没有介绍,我不理解的地方也有很多,这篇文章只是我的一个学习笔记而已。

0 0
原创粉丝点击