Linux中断子系统(二)—— 通用框架处理


1、数据结构分析


(1)中断描述符结构 struct irq_desc


        1)打开CONFIG_SPARSE_IRQ宏(中断编号不连续),中断描述符以radix-
tree 来组织,用户在初始化时进行动态分配,然后再插入 radix-tree 中;
        2)关闭CONFIG_SPARSE_IRQ宏(中断编号连续),中断描述符以数组的
形式组织,并且已经分配好;
        3)不管哪种形式,最终都可以通过linuxirq号来找到对应的中断描述符;


(2)struct irq_chip 用于对中断控制器的硬件操作;struct irq_domain 与中断控制器对应,完成的工作是硬件中断号到Linux irq的映射;struct irq_domain_ops是中断中对应的操作函数;这三者主要在中断控制器驱动中进行初始化设置。


(3)struct irqaction 中handler的设置,用于指向我们设备驱动程序中的中断处理函数,在设备申请注册中断的过程中进行设置


(4)结构体之间的关系
struct irq_desc 包含 struct irqaction 和 struct irq_data

        struct irq_data 包含 struct irq_domain 和 struct irq_chip

                struct irq_domain 包含 struct irq_domain ops

                        struct irqaction 包含 设备驱动中的中断处理函数——irq_handler_t


(5)中断的处理主要有以下几个功能模块:
        1)硬件中断号到Linux irq 中断号的映射,并创建好 irq_desc中断描述符;
        2)中断注册时,先获取设备的中断号,根据中断号找到对应的irq_desc,并将设备的中断处理函数添加到irq_desc中;
        3)设备触发中断信号时,根据硬件中断号得到Linux irq中断号,找到对应的irq_desc,最终调用到设备的中断处理函数;


2、设备硬件中断号到Linux irq 中断号的映射


(1)platform_get_irq(struct platform_device *dev,unsigned int num)

/**drivers\base\Platform.c **/
platform_get_irq(struct platform_device *dev,unsigned int num)
    ---→_platform_get_irq(dev,num)
        ---→of_irq_get(dev->dev.of_node,num)
                //  返回 Linux irq number-driverslof\Irq.c
            ---→of_irq_parse_one(dev,index,&oirq)
                //  从设备数据中解析中断信息
            ---→irq_find_host(oirq.np)
                //  遍历 irq_domain_list链表,找到匹配的irq_domain
            ---→irq_create_of_mapping(&oirq)    //   kernel\irq\lrqdomain.c
                //  irq_of_parse_and_map也可以调用到该函数,其将中断映射到Linux 空间;irq_of_parse_and_map是个wrapper,更容易调用——drivers\of\Irq.c


(2)  irq_create_of_mapping(&oirq)

irq_create_of_mapping(&oirq)
    ---→irq_create_fwspec_mapping(&fwspec)//kernel\irq\lrqdomain.c---            
        ---→irq_find_matching_fwspec(fwspec,DOMAIN_BUS_WIRED)
            // 遍历irq_domain_list链表,找到匹配的 irq domain
        ---→irq_domain_translate(domain,fwspec,&hwirq,&type)
            ---→d->ops->translate(d,fwspec,hwirq,type)
            // 回调到 gic 驱动中的 gic_irq_domain_translate 函数,解析中断源的相关信息:中断号、触发方式等
        ---→irq_find_mapping(domain,hwirq)
             // 从硬件 irq number 中找到 Linux irq


(2) Virq=irq_find_mapping(domain,hwirq)

Virq=irq_find_mapping(domain,hwirq)
    ---→若中断映射已建立好,则返回中断
    ---→irq_domain_alloc_irqs(domain,,,fwspec)
        // 创建映射关系
        ---→_irq_domain_alloc_irqs()
            // 为irq_domain 分配Irq,并始化数据结构


(3)_irq_domain_alloc_irqs()

_irq_domain_alloc_irqs()
    ---→irq_domain_alloc_descs(irq_base,nr_irqs,0,node,affinity);
        // 分配和初始化中断描述符irq_desc
        ---→_irq_alloc_descs()
            ---→alloc_descs()     ————  kernel\irq\Irqdesc.c
    ---→irq_domain_alloc_irqs_hierarchy(domain,virq,nr_irqs,arg)
        ---→domain->ops->alloc(domain,irq_base,nr_irqs,arg)


(4)
        1)硬件设备的中断信息都在设备树device tree中进行了描述,在系统启动过程中,这些信息都已经加载到内存中并得到了解析;
        2)驱动中通常会使用platform_get_irq或 irq_of_parse_and_map 接口,去根据设备树的信息去创建映射关系(硬件中断号到linux irq 中断号映射)
        3)如果已经创建好了映射,那么可以直接进行返回linux irq中断号了,否则的话需要 irq_domain_alloc_irqs来创建映射关系;
        irq_domain_alloc_irqs 完成两个工作:
                a)针对linux irq 中断号创建一个irq_desc中断描述符;
                b)调用domain->ops->alloc函数来完成映射,在ARM GICv2驱动中对应gic_irq_domain_alloc 函数


(5)gic_irq_domain_alloc()

gic_irq_domain_alloc()          // drivers\irqchip\Irq-gic.c
    ---→gic_irq_domain_translate(domain, fwspec,&hwirq,&type)
        // 解析出硬件中断号,以及中断触发类型(边缘触发、电平触发等))
    ---→gic_irq_domain_map(domain,virq +i,hwirq +i)
        ---→hw<32,为私有中断
            irq_domain_set_info(,,,,,handle_percpu_devid_irq,,)
            // 私有中断的 处理函数为handle_percpu_devid_irq
        ---→hw>=32,为共享中断
            irq_domain_set_info(,,,,,handle_fasteoi_irq,,)
            // 共享中断的 处理函数为 handle_fasteoi_irq
            ---→irq_domain_set_info()
            // 为domain 中的virq 设置完整数据
                ---→irq_domain_set_hwirq_and_chip
            // 设置 irq_desc->irq_data 中的hwirq、chip和chip_data 字段
                ---→_irq_set_handler
            // 设置irq_desc->handle_irq 字段
                ---→irq_set_handler_data
            // 设置irq_desc->irq_common_data.handler_data 字段


(6)上述函数执行完成后,完成了两大工作:
        1)硬件中断号与Linux中断号完成映射,并为Linux 中断号创建了 irq_desc中断描述符;
        2)数据结构的绑定及初始化,关键的地方是设置了中断处理往上执行的入口


3、中断注册


(1)request_irq()

--→request_irq()
    /** include\linux\lnterrupt.h **/
    ----→request_threaded_irq(irq, handler, NULL, flags,name,dev)
        //  此调用分配中断资源并启用中断线和IRQ 处理 ———— kernel\irq\Manage.c
        ----→irq_to_desc(irq)
        //  根据Linux 中断号,获取中断描述符irq_desc
        ----→action =kzalloc(sizeof(struct irqaction),GFP_KERNEL)
        //  分配irqaction 结构,并进行填充
        ----→setup_irq(irq,desc,action)      ———— kernel\irq\Manage.c
        //  注册中断


(2)_setup_irq(irq,desc,action)

_setup_irq(irq,desc,action)
    ----→irq_settings_is_nested_thread(desc)
        //  检查中断是否嵌套在另一个中断线程中
        ----→nested--N--→irq_settings_can_thread(desc)
            //  判断条件--→中断是否可以线程化
            ----→rq_setup_forced_threading(new)
                //  中断强制线程化
        ----→new->thread_fn&&!nested--Y--→setup_irq_thread(new,irq,false)----→kthread_create(irq_thread,,,,)
            //  为中断创建一个内核线程
        ----→!desc->action --Y--> irq_request_resources(desc)
            //  第一个irqaction 需要请求分配资源
        ----→old_ptr=&desc->action--Y-->
            //  中断需共享
        ---->do{
                thread_mask |=old->thread_mask;old_ptr=&old->next;
                old=*old_ptr;
                }while (old);

            //  共享中断,irqaction会形成链表,当所有共享中断处理完之后才能 unmask中
断;在末尾添加一个新中断
        ----→!shared            --Y-->       //  中断非共享情况
            ----→init_waitqueue_head(&desc->wait_for_threads)
            ----→irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS)
            ----→if (new->flags & IRQF_XXX){
                    irqd_XXX0;
                    }
            //  非共享中断处理,包括中断balance、percpu等设置


_setup_irq 用于完成中断的相关设置,包括中断线程化的处理:
        1)中断线程化用于减少系统关中断的时间,增强系统的实时性;
        2)ARM64默认开启了CONFIG_IRQ_FORCED_THREADING,引导参数传入threadirqs时,则除了IRQF_NO_THREAD外的中断,其他的都将强制线程化处理;
        3)中断线程化会为每个中断都创建一个内核线程,如果中断进行共享,对应irqaction 将连接成链表,每个irqaction 都有thread_mask 位图字段,当所有共享中断都处理完成后才能unmask中断,解除中断屏蔽;


4、中断处理


generic_handle_irq
//  为一个irq来触发handler————kernel\irq\lrqdesc.c

generic_handle_irq
        //  为一个irq来触发handler-kernel\irq\lrqdesc.c
    ----→irq_to_desc
        //  根据中断号获取 irq_desc
    ----→generic_handle_irq_desc
        //  IRQ layer来处理中断
        ----→desc->handle_irq(desc)
            //  irq_domain_set_info 建立 map 阶段设置了函数指针
            ----→handle_fasteoi_irq
                //  共享中断处理 ———— kernel\irq\Chip.c
                ----→handle_irq_event(desc)
                    ----→handle_irq_event_percpu(desc)
                        ----→_handle_irq_event_percpu(desc,&flags)
                            //  kernel\irq\Handle.c
                            ----→action->handler(irq,action->dev_id)
                            //  遍历action 链表,并执行处理函数
                            ----→case IRQ_WAKE_THREAD:
                                    _irq_wake_thread(desc,action);
                                    //  遍历 action链表,若中断为线程化处理,则还需唤醒操作            
            ---→handle_percpu_devid_irq
                //  per-cpu中断处理--kernel\irq\Chip.c
                ----→chip->irq_ack//中断控制器进行ack确认
                ----→action->handler(irq,raw_cpu_ptr(action->percpu_dev_id))
                //  调用注册的中断函数
                ----→chip->irq_eoi
                //  中断控制器进行eoi确认


        1)handle_fasteoi_irq:处理共享中断,并且遍历irqaction链表,逐个调用action->handler(函数,这个函数正是设备驱动程序调用 request_irq/request_threaded_irq 接口注册的中断处理函数,此外如果中断线程化处理的话,还会调用_irq_wake_thread()唤醒内核线程;
        2)handle_percpu_devid_irq:处理 per-CPU中断处理,在这个过程中会分别调用中断控制器的处理函数进行硬件操作,该函数调用action->handler()来进行中断处理;


中断线程化处理后的唤醒流程:

参考链接:kernel 中断分析五——irq_thread_Burning Water-CSDN博客
_handle_irq_event_percpu

_handle_irq_event_percpu
    ----→_irq_wake_thread(desc,action)
        ----→(唤醒)irq_thread(void *data)
            //  kernel\irq\Manage.c
            ----→handler_fn =irq_forced_thread_fn
                //  没有明确申请thread interrupt的中断,需要将硬中断上下文的bh/抢占关闭
            ----→handler_fn=irq_thread_fn
                //  多数线程化的中断都会被抢占然后睡眠
                ----→action->thread_fn(action->irq,action->dev_id)
                    //  运行最终的线程化处理函数
                    ----→irq_wait_for_interrupt(action)
                        ----→set_current_state(TASK_INTERRUPTIBLE)
                            //  设置线程状态
                        ----→(test_and_clear_bit(IRQTF_RUNTHREAD,&action->thread_flags)
                            //  测试中断线程的唤醒条件
                        ----→schedule()
                            //  不满足唤醒条件则让出CPU


        1)irq_thread 内核线程将 while(lirq_wait_for_interrupt)循环进行中断的处理,当满足条件时,执行handler_fn,在该函数中最终调用 action->thread_fn,也就是完成了中断的处理;
        2)irq_wait_for_interrupt函数,将会判断中断线程的唤醒条件,如果满足了,则将当前任务设置成TASK_RUNNING状态,并返回0,这样就能执行中断的处理,否则就调用schedule()进行调度,让出CPU,并将任务设置成TASK_INTERRUPTIBLE可中断睡眠状态;


5、总结


中断的处理,总体来说可以分为两部分来看:


        从上到下:围绕irq_desc中断描述符建立好连接关系,这个过程就包括:中断源信息的解析(设备树),硬件中断号到Linux中断号的映射关系、irq_desc结构的分配及初始化(内部各个结构的组织关系)、中断的注册(填充 irq_desc结构,包括handler处理函数)等,总而言之,就是完成静态关系创建,为中断处理做好准备;
        从下到上:当外设触发中断信号时,中断控制器接收到信号并发送到处理器,此时处理器进行异常模式切换,并逐步从处理器架构相关代码逐级回调。如果涉及到中断线程化,则还需要进行中断内核线程的唤醒操作,最终完成中断处理函数的执行。

你可能感兴趣的:(Linux中断相关知识,linux,中断,C++)