美文网首页
Linux内核中断之中断调用流程

Linux内核中断之中断调用流程

作者: 小田BSP | 来源:发表于2021-05-11 22:31 被阅读0次

本文基于RockPI 4A单板Linux4.4内核介绍中断调用流程。

一、异常向量表

ARMv8包括两种运行状态:AArch64和AArch32。

AArch64中不再使用AArch32中的7种特权模式,而是提出了Exception Levels的概念,包括:

1)EL0:用于用户态程序,权限最低

2)EL1:给内核使用,权限稍高

3)EL2:虚拟化相关,权限更高

4)EL3:安全相关,权限最高

Linux内核中一般只使用EL0和EL1。

AArch64异常向量表中的异常包括:

1)Synchronous exception(同步异常)

2)SError

3)IRQ

4)FIQ

注:SError、IRQ和FIQ属于异步异常。

在Linux内核中,在arch/arm64/kernel/entry.S文件中定义了异常向量表,内容如下:

/*
 * Exception vectors.
 */
    .pushsection ".entry.text", "ax"

    .align  11
ENTRY(vectors)
    ...                                 ## 省略了部分EL1和EL0的异常向量
    kernel_ventry   1, sync             // Synchronous EL1h ,对应el1_sync
    kernel_ventry   1, irq              // IRQ EL1h         ,对应el1_irq
    kernel_ventry   1, fiq_invalid      // FIQ EL1h         ,对应el1_fiq_invalid
    kernel_ventry   1, error_invalid    // Error EL1h       ,对应el1_error_invalid
    ...
END(vectors)

二、中断调用流程

选取el1_irq()函数介绍Linux内核中断的调用流程。

文件:arch/arm64/kernel/entry.S,调用流程如下:

image.png

1、handle_irq()初始化

DTS解析阶段完成handle_irq()函数的初始化,流程如下:

of_platform_populate()->                                        ##platform.c
    of_platform_bus_create()->
        of_platform_device_create_pdata()->
            of_device_alloc()->
                of_irq_to_resource_table()->
                    of_irq_to_resource()->
                        irq_of_parse_and_map()->
                            irq_create_of_mapping()->
                                irq_create_fwspec_mapping()->
                                    irq_domain_alloc_irqs()->
                                        irq_domain_alloc_irqs_recursive()->
                                            domain->ops->alloc()   ## 对应GICv3的gic_irq_domain_alloc()
                                            ::gic_irq_domain_alloc()->
                                                gic_irq_domain_map()

gic_irq_domain_map()函数中完成了handle_irq()函数的赋值,具体执行如下:

static int gic_irq_domain_map(struct irq_domain *d, unsigned int irq,
                  irq_hw_number_t hw)
{
    ...
    /* PPIs */
    if (hw < 32) {
        ..
        
        ## 1.赋值PPI的handle_irq = handle_percpu_devid_irq
        irq_domain_set_info(d, irq, hw, chip, d->host_data,
                    handle_percpu_devid_irq, NULL, NULL);
        ..
    }
    /* SPIs */
    if (hw >= 32 && hw < gic_data.irq_nr) {
    
        ## 2.赋值SPI的handle_irq = handle_fasteoi_irq
        irq_domain_set_info(d, irq, hw, chip, d->host_data,
                    handle_fasteoi_irq, NULL, NULL);
        ...
    }
    /* LPIs */
    if (hw >= 8192 && hw < GIC_ID_NR) {
        ...
        
        ## 3.赋值LPI的handle_irq = handle_fasteoi_irq
        irq_domain_set_info(d, irq, hw, chip, d->host_data,
                    handle_fasteoi_irq, NULL, NULL);
    }

    return 0;
}

2、handle_irq()实现

以共享外设中断SPI的中断处理函数handle_fasteoi_irq()为例,继续跟踪中断的执行过程。

handle_fasteoi_irq()->         ## kernel/irq/chip.c
    handle_irq_event()->
        handle_irq_event_percpu()->

handle_irq_event_percpu()函数会调用已经注册的中断处理函数,同时唤醒irq_thread线程。

irqreturn_t handle_irq_event_percpu(struct irq_desc *desc)
{
    ...
    while (action) {
        ...
        trace_irq_handler_entry(irq, action);
        ## 1. 执行已经注册的对应的中断处理函数                   ---- (重点)
        res = action->handler(irq, action->dev_id);
        trace_irq_handler_exit(irq, action, res);
        ...
        switch (res) {
        case IRQ_WAKE_THREAD:
            ## 2.唤醒irq_thread线程
            __irq_wake_thread(desc, action);
        ...
        default:
            break;
        }
        ...
    }
    ...
}

3、中断处理线程

在使用request_threaded_irq()函数申请中断时,会创建一个irq_thread线程,调用流程如下:

request_threaded_irq()->         ## kernel/irq/manage.c
    __setup_irq()->
        setup_irq_thread()->
             kthread_create(irq_thread, new, "irq/%d-%s", irq,new->name)  ## 创建了一个irq_thread线程

irq_thread线程平时在睡眠状态,等待handle_irq_event_percpu()函数唤醒,进一步执行已注册的中断处理线程函数。

static int irq_thread(void *data)
{
    ...
    if (force_irqthreads && test_bit(IRQTF_FORCED_THREAD,
                    &action->thread_flags))
        handler_fn = irq_forced_thread_fn;
    else
        handler_fn = irq_thread_fn;   ## 1. 赋值中断处理线程

    init_task_work(&on_exit_work, irq_thread_dtor);
    task_work_add(current, &on_exit_work, false);

    irq_thread_check_affinity(desc, action);

    while (!irq_wait_for_interrupt(action)) {
        ...
        action_ret = handler_fn(desc, action); ## 2.执行已注册的对应的中断处理线程函数  ---- (重点)
        ...
    }
    ...
}

三、应用举例

使用DRM框架中HDMI中断验证中断调用流程。

文件:drivers\gpu\drm\bridge\synopsys\dw-hdmi.c

int dw_hdmi_bind(struct device *dev, struct device *master,
         void *data, struct drm_encoder *encoder,
         struct resource *iores, int irq,
         const struct dw_hdmi_plat_data *plat_data)
{
        ...
        ## 申请中断,并传入中断处理函数dw_hdmi_hardirq() 和 中断处理线程函数dw_hdmi_irq()
        ret = devm_request_threaded_irq(dev, irq, dw_hdmi_hardirq,
                    dw_hdmi_irq, IRQF_SHARED,
                    dev_name(dev), hdmi);
        ...
}

在中断处理函数dw_hdmi_hardirq()和中断处理线程函数dw_hdmi_irq中增加dump_stack()调用(注:仅限于调试验证)。

插入HDMI线,系统启动后,显示中断调用流程的日志如下:

[    7.980327] Exception stack(0xffffffc0796979d0 to 0xffffffc079697b00)
[    8.013527] Hardware name: ROCK PI 4A 2 (DT)
[    8.013911] Call trace:
[    8.014140] [<ffffff80080888d8>] dump_backtrace+0x0/0x220
[    8.014621] [<ffffff8008088b1c>] show_stack+0x24/0x30
[    8.015078] [<ffffff800856ebec>] dump_stack+0x98/0xc0
[    8.015526] [<ffffff80086c1fa8>] dw_hdmi_hardirq+0xf8/0xfc              ## 2.中断处理函数
[    8.016018] [<ffffff80080f1664>] handle_irq_event_percpu+0xf4/0x1f0
[    8.016578] [<ffffff80080f17b0>] handle_irq_event+0x50/0x80
[    8.017071] [<ffffff80080f4fd4>] handle_fasteoi_irq+0xcc/0x134
[    8.017586] [<ffffff80080f0b60>] generic_handle_irq+0x2c/0x44
[    8.018100] [<ffffff80080f0edc>] __handle_domain_irq+0xb4/0xb8
[    8.018615] [<ffffff8008080e70>] gic_handle_irq+0xc8/0x180
[    8.019104] Exception stack(0xffffffc079697b40 to 0xffffffc079697c70)
[    8.019676] 7b40: 0000000000000001 00000000000068c1 0000000000000000 ffffffc079697c88
[    8.020373] 7b60: 0000000000000000 0000000000000000 ffffffc077fd3b50 ffffffc077fd3b78
[    8.021069] 7b80: ffffffc077fd3b80 0000000000000000 ffffff800921ee78 00000000ffffffff
[    8.021766] 7ba0: ffffffc0783ad1d0 ffffff8009d30020 ffffff8009d30020 0000000000000000
[    8.022462] 7bc0: 0000000000000028 0000000000007fff 0000000000000003 ffffffc077f908e0
[    8.023159] 7be0: 00000000000068c1 0000000002400040 ffffffc077f908d8 ffffff80093b20f5
[    8.023855] 7c00: ffffff80093a7740 00000000000068c1 0000000000000001 0000000000000008
[    8.024552] 7c20: ffffff80092102c8 ffffffc079697c70 ffffff800857402c ffffffc079697c70
[    8.025249] 7c40: ffffff8008573fe4 0000000020000045 ffffffc079697c70 ffffff80081732d8
[    8.025944] 7c60: ffffffffffffffff ffffffc079413800
[    8.026377] [<ffffff80080827b4>] el1_irq+0xb4/0x140                    ## 1.异常处理入口
[    8.026813] [<ffffff8008573fe4>] __radix_tree_lookup+0x70/0xa4
[    8.027329] [<ffffff80081737dc>] find_get_entry+0x2c/0xbc
[    8.027811] [<ffffff800817394c>] pagecache_get_page+0x54/0x1b8
[    8.028326] [<ffffff8008483360>] btrfs_test_extent_io+0x8c/0x4b4
[    8.028863] [<ffffff80091396cc>] init_btrfs_fs+0xe4/0x168
[    8.029344] [<ffffff80080831cc>] do_one_initcall+0x18c/0x194
[    8.029848] [<ffffff8009110e10>] kernel_init_freeable+0x228/0x22c
[    8.030385] [<ffffff8008c7519c>] kernel_init+0x18/0x100
[    8.030854] [<ffffff8008082ef0>] ret_from_fork+0x10/0x20

[    8.065185] CPU: 0 PID: 97 Comm: irq/55-ff940000 Not tainted 4.4.154-00037-gdee50b698ce8-dirty #72
[    8.065978] Hardware name: ROCK PI 4A 2 (DT)
[    8.066364] Call trace:
[    8.066603] [<ffffff80080888d8>] dump_backtrace+0x0/0x220
[    8.067086] [<ffffff8008088b1c>] show_stack+0x24/0x30
[    8.067546] [<ffffff800856ebec>] dump_stack+0x98/0xc0
[    8.068007] [<ffffff80086c59f0>] dw_hdmi_irq+0x1e8/0x228           ## 2\. 中断处理线程函数
[    8.068490] [<ffffff80080f2550>] irq_thread_fn+0x30/0x54
[    8.068960] [<ffffff80080f28ac>] irq_thread+0x17c/0x1b0            ## 1\. irq_thread 线程
[    8.069430] [<ffffff80080ba310>] kthread+0xe0/0xf0
[    8.069864] [<ffffff8008082ef0>] ret_from_fork+0x10/0x20

相关文章

  • Linux内核中断之中断调用流程

    本文基于RockPI 4A单板Linux4.4内核介绍中断调用流程。 一、异常向量表 ARMv8包括两种运行状态:...

  • Linux内核中断之中断初始化

    本文基于RockPI 4A单板Linux4.4内核介绍中断初始化流程。 一、内核中断初始化 文件:kernel\i...

  • day7 文件系统剩余部分

    1. exec.c exec大概流程是,调用中断陷入内核态后,在内核态内中断处理程序从eax寄存器里发现是调用ex...

  • Linux内核中断之获取中断号

    Linux内核中可使用platform_get_irq()函数获取dts文件中设置的中断号。 函数原型:int p...

  • 2021-04-03

    Linux0.11内核阅读——中断篇 中断前处理及中断现场恢复中断执行过程硬中断asm.strap.c软中断sys...

  • 品味内核中断上下半部

    在《初尝内核中断》里我们了解了Linux内核中断模块的实现,也体验了一番共享中断的魅力,但上面这样的中断程序把任务...

  • 系统调用 - syscall

    linux系统调用是通过中断实现的,软中断指令int发起中断信号。linux只占用一个中断向量号,即:0x80。系...

  • 分析system_call中断处理过程

    网易云课堂《Linux内核分析》作业 实验目的: 使用gdb跟踪分析一个系统调用中断处理过程,分析系统调用从sys...

  • Linux内核中断之中断申请接口

    本文基于RockPI 4A单板Linux4.4内核介绍中断申请的常用接口函数。 一、request_threade...

  • 1.Linux内核简介

    Linux内核简介 内核是操作系统的核心 ,其主要功能有: 响应中断,执行中断服务程序 管理多个进程,调度和分享处...

网友评论

      本文标题:Linux内核中断之中断调用流程

      本文链接:https://www.haomeiwen.com/subject/xwtodltx.html