am33xx linux中断处理流程

运行环境

  kernel 5.10 

  CPU Ti am33xx

linux中断的3个结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
struct irq_desc {
    struct irq_common_data  irq_common_data;
    struct irq_data     irq_data;
    unsigned int __percpu   *kstat_irqs;
    irq_flow_handler_t  handle_irq;
    struct irqaction    *action;    /* IRQ action list */   // 里面存放了用户注册的中断服务代码
    unsigned int        status_use_accessors;
    unsigned int        core_internal_state__do_not_mess_with_it;
    unsigned int        depth;      /* nested irq disables */
    unsigned int        wake_depth; /* nested wake enables */
    unsigned int        tot_count;
    unsigned int        irq_count;  /* For detecting broken IRQs */
    unsigned long       last_unhandled; /* Aging timer for unhandled count */
    unsigned int        irqs_unhandled;
    atomic_t        threads_handled;
    int         threads_handled_last;
    raw_spinlock_t      lock;
    struct cpumask      *percpu_enabled;
    const struct cpumask    *percpu_affinity;
#ifdef CONFIG_SMP
    const struct cpumask    *affinity_hint;
    struct irq_affinity_notify *affinity_notify;
#ifdef CONFIG_GENERIC_PENDING_IRQ
    cpumask_var_t       pending_mask;
#endif
#endif
    unsigned long       threads_oneshot;
    atomic_t        threads_active;
    wait_queue_head_t       wait_for_threads;
#ifdef CONFIG_PM_SLEEP
    unsigned int        nr_actions;
    unsigned int        no_suspend_depth;
    unsigned int        cond_suspend_depth;
    unsigned int        force_resume_depth;
#endif
#ifdef CONFIG_PROC_FS
    struct proc_dir_entry   *dir;
#endif
#ifdef CONFIG_GENERIC_IRQ_DEBUGFS
    struct dentry       *debugfs_file;
    const char      *dev_name;
#endif
#ifdef CONFIG_SPARSE_IRQ
    struct rcu_head     rcu;
    struct kobject      kobj;
#endif
    struct mutex        request_mutex;
    int         parent_irq;
    struct module       *owner;
    const char      *name;
} ____cacheline_internodealigned_in_smp;

  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct irqaction {
    irq_handler_t       handler;  // 用户注册的中断服务代码, 中断发生时就会运行这个中断处理函数
    void            *dev_id;
    void __percpu       *percpu_dev_id;
    struct irqaction    *next;
    irq_handler_t       thread_fn;
    struct task_struct  *thread;
    struct irqaction    *secondary;
    unsigned int        irq;    //hw中断号,
    unsigned int        flags;  //中断标志,注册时设置,比如上升沿中断,下降沿中断等
    unsigned long       thread_flags;
    unsigned long       thread_mask;
    const char      *name;  //中断名称,产生中断的硬件的名字
    struct proc_dir_entry   *dir;  // proc/irq/
} ____cacheline_internodealigned_in_smp;

  

1
2
3
4
5
6
7
8
9
10
11
12
struct irq_data {
    u32         mask;
    unsigned int        irq;
    unsigned long       hwirq;
    struct irq_common_data  *common;
    struct irq_chip     *chip;    // 芯片端的中断处理函数
    struct irq_domain   *domain;    // 代表一个interrupt controller 设备树中的INTC
#ifdef  CONFIG_IRQ_DOMAIN_HIERARCHY
    struct irq_data     *parent_data;
#endif
    void            *chip_data;
};

  整体框图如下:

 

 

 

linux硬件中断处理流程

1. 外设产生中断

2. CPU触发中断

3. 跳转到异常向量入口:

  保存现场

  执行中断服务

  恢复现场

 -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

我们主要关注如何执行中断服务,在arch\arm\kernel\entry-armv.S中,有如下几条语句

1
2
3
4
5
6
7
8
9
    .macro  irq_handler
#ifdef CONFIG_GENERIC_IRQ_MULTI_HANDLER
    ldr r1, =handle_arch_irq
    mov r0, sp
    badr    lr, 9997f
    ldr pc, [r1]
#else
    arch_irq_handler_default
#endif

其中 handle_arch_irq 即为CPU处理中断时会调用到的硬件中断服务入口,handle_arch_irq在 kernel/irq/handle.c 中被赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#ifdef CONFIG_GENERIC_IRQ_MULTI_HANDLER
#ifndef CONFIG_IRQCHIP_XILINX_INTC_MODULE_SUPPORT_EXPERIMENTAL
int __init set_handle_irq(void (*handle_irq)(struct pt_regs *))
#else
int set_handle_irq(void (*handle_irq)(struct pt_regs *))
#endif
{
    if (handle_arch_irq)
        return -EBUSY;
 
    handle_arch_irq = handle_irq;
    return 0;
}
#endif

 

set_handle_irq 被谁调用?主要由CPU厂家的BSP工程师调用,比如TI的AM33xx系列平台,在 drivers/irqchip/irq-omap-intc.c 中的以下接口调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
static int __init intc_of_init(struct device_node *node,
                 struct device_node *parent)
{
    int ret;
 
    omap_nr_pending = 3;
    omap_nr_irqs = 96;
 
    if (WARN_ON(!node))
        return -ENODEV;
 
    if (of_device_is_compatible(node, "ti,dm814-intc") ||
        of_device_is_compatible(node, "ti,dm816-intc") ||
        of_device_is_compatible(node, "ti,am33xx-intc")) {
        omap_nr_irqs = 128;
        omap_nr_pending = 4;
    }
 
    ret = omap_init_irq(-1, of_node_get(node));
    if (ret < 0)
        return ret;
 
    set_handle_irq(omap_intc_handle_irq);   // 设置硬中断服务函数入口
 
    return 0;
}

 

omap_intc_handle_irq便是am33xx触发中断后,需要响应的中断服务入口,该入口做了哪些工作?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
static asmlinkage void __exception_irq_entry
omap_intc_handle_irq(struct pt_regs *regs)
{
    extern unsigned long irq_err_count;
    u32 irqnr;
 
    irqnr = intc_readl(INTC_SIR);  // 获取硬件中断号
 
    /*
     * A spurious IRQ can result if interrupt that triggered the
     * sorting is no longer active during the sorting (10 INTC
     * functional clock cycles after interrupt assertion). Or a
     * change in interrupt mask affected the result during sorting
     * time. There is no special handling required except ignoring
     * the SIR register value just read and retrying.
     * See section 6.2.5 of AM335x TRM Literature Number: SPRUH73K
     *
     * Many a times, a spurious interrupt situation has been fixed
     * by adding a flush for the posted write acking the IRQ in
     * the device driver. Typically, this is going be the device
     * driver whose interrupt was handled just before the spurious
     * IRQ occurred. Pay attention to those device drivers if you
     * run into hitting the spurious IRQ condition below.
     */
    if (unlikely((irqnr & SPURIOUSIRQ_MASK) == SPURIOUSIRQ_MASK)) {
        pr_err_once("%s: spurious irq!\n", __func__);
        irq_err_count++;
        omap_ack_irq(NULL);
        return;
    }
 
    irqnr &= ACTIVEIRQ_MASK;
    handle_domain_irq(domain, irqnr, regs); // 根据硬件中断号,在domain链表中查找映射好的IRQ number, 实现原理是怎样的?
}

domain是如何实现HW 中断号和 IRQ number映射的? 在解析设备树的时候,会调用 irq_of_parse_and_map 接口创建映射;详情参考 linux中断号映射

handle_domain_irq 调用了 __handle_domain_irq, 在__handle_domain_irq中,开始调用硬中断服务函数,也就是中断上半部

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
int __handle_domain_irq(struct irq_domain *domain, unsigned int hwirq,
            bool lookup, struct pt_regs *regs)
{
    struct pt_regs *old_regs = set_irq_regs(regs);
    unsigned int irq = hwirq;
    int ret = 0;
 
    irq_enter();
 
#ifdef CONFIG_IRQ_DOMAIN
    if (lookup)
        irq = irq_find_mapping(domain, hwirq);  // 根据硬中断号,在映射表中找到对应的 IRQ number
#endif
 
    /*
     * Some hardware gives randomly wrong interrupts.  Rather
     * than crashing, do something sensible.
     */
    if (unlikely(!irq || irq >= nr_irqs)) {
        ack_bad_irq(irq);
        ret = -EINVAL;
    } else {
        generic_handle_irq(irq);  // 根据IRQ number,查找中断服务,并执行
    }
 
    irq_exit();  // 退出硬件中断服务,然后 invoke_softirq(),再 __do_softirq(),唤醒softirq队列,进入下半部开始执行
    set_irq_regs(old_regs);
    return ret;
}

  他会调用  generic_handle_irq(irq) , 以下为 generic_handle_irq 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int generic_handle_irq(unsigned int irq)
{
    struct irq_desc *desc = irq_to_desc(irq); // 根据IRQ number 找到 中断描述符
    struct irq_data *data;
 
    if (!desc)
        return -EINVAL;
 
    data = irq_desc_get_irq_data(desc);
    if (WARN_ON_ONCE(!in_irq() && handle_enforce_irqctx(data)))
        return -EPERM;
 
    generic_handle_irq_desc(desc); // 根据中断描述符,找到对应的 handler
    return 0;
}

generic_handle_irq_desc 会调用 desc结构体中的 hande_irq 成员: desc->handle_irq(desc); 他指向哪 ?

desc->handle_irq(desc) 最后会调用到 irqaction 结构体中的 handler 成员,也就是用户通过 request_irq (request_irq详解) 接口注册的中断服务程序,附上网上找的一份简单示意图(kernel2.6

1
<em id="__mceDel"><br><br></em>

 

 

 

 

 desc->handle_irq(desc) 是如何关联到 irqaction 结构体中的 handler 成员?

 先从设备端看:

拿AM33xx GPIO举例:

在 gpio-omap.c 中的 omap_gpio_probe 里面,会对 irq_chip 结构体的 irq_set_type 赋值 omap_gpio_irq_type,然后将 am33xx芯片端的gpio 中断处理相关的函数注册到linux kernel,kernel调用 desc->handle_irq(desc) 时,即可调用到 am33xx xinp端的 gpio 中断处理函数(具体细节接下来分析);

omap_gpio_irq_type 函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
static int omap_gpio_irq_type(struct irq_data *d, unsigned type)
{
    struct gpio_bank *bank = omap_irq_data_get_bank(d);
    int retval;
    unsigned long flags;
    unsigned offset = d->hwirq;
 
    if (type & ~IRQ_TYPE_SENSE_MASK)
        return -EINVAL;
 
    if (!bank->regs->leveldetect0 &&
        (type & (IRQ_TYPE_LEVEL_LOW|IRQ_TYPE_LEVEL_HIGH)))
        return -EINVAL;
 
    raw_spin_lock_irqsave(&bank->lock, flags);
    retval = omap_set_gpio_triggering(bank, offset, type);
    if (retval) {
        raw_spin_unlock_irqrestore(&bank->lock, flags);
        goto error;
    }
    omap_gpio_init_irq(bank, offset);
    if (!omap_gpio_is_input(bank, offset)) {
        raw_spin_unlock_irqrestore(&bank->lock, flags);
        retval = -EINVAL;
        goto error;
    }
    raw_spin_unlock_irqrestore(&bank->lock, flags);
 
    if (type & (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_LEVEL_HIGH))
        irq_set_handler_locked(d, handle_level_irq);    // 电平触发中断
    else if (type & (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING))
        /*
         * Edge IRQs are already cleared/acked in irq_handler and
         * not need to be masked, as result handle_edge_irq()
         * logic is excessed here and may cause lose of interrupts.
         * So just use handle_simple_irq.
         */
        irq_set_handler_locked(d, handle_simple_irq);   // 边沿触发中断
 
    return 0;
 
error:
    return retval;
}   

  该函数主要是设置了 gpio 电平触发, 边沿触发的中断处理服务,先拿边沿触发分析,看看 handle_simple_irq 函数是如何一步步调用到 irqaction 结构体中的 handler 成员的(也就是用户通过request_irq 申请的中断服务):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void handle_simple_irq(struct irq_desc *desc)
{
    raw_spin_lock(&desc->lock);
 
    if (!irq_may_run(desc))
        goto out_unlock;
 
    desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);
 
    if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data))) {
        desc->istate |= IRQS_PENDING;
        goto out_unlock;
    }
 
    kstat_incr_irqs_this_cpu(desc);  // 设置处理标记,避免多次重复进入中断处理
    handle_irq_event(desc);    // 中断服务相关
 
out_unlock:
    raw_spin_unlock(&desc->lock);
}

  再来看看 handle_irq_event(desc) 做了啥 irqreturn_t handle_irq_event(struct irq_desc *desc)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
    irqreturn_t ret;
 
    desc->istate &= ~IRQS_PENDING;
    irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);
    raw_spin_unlock(&desc->lock);
 
    ret = handle_irq_event_percpu(desc);  // 进入中断服务
 
    raw_spin_lock(&desc->lock);
    irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);
    return ret;
}
 
 
irqreturn_t handle_irq_event_percpu(struct irq_desc *desc)
{
    irqreturn_t retval;
    unsigned int flags = 0;
 
    retval = __handle_irq_event_percpu(desc, &flags);   // 进入中断服务
 
    add_interrupt_randomness(desc->irq_data.irq);
 
    if (!noirqdebug)
        note_interrupt(desc, retval);
    return retval;
}<br>

  接下来就真正调用到了 irqaction 结构体中的 handler 成员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
irqreturn_t __handle_irq_event_percpu(struct irq_desc *desc, unsigned int *flags)
{
    irqreturn_t retval = IRQ_NONE;
    unsigned int irq = desc->irq_data.irq;
    struct irqaction *action;
 
    record_irq_time(desc);
 
    for_each_action_of_desc(desc, action) {   // 轮询该中断描述符里面的所有 action
        irqreturn_t res;
 
        /*
         * If this IRQ would be threaded under force_irqthreads, mark it so.
         */
        if (irq_settings_can_thread(desc) &&
            !(action->flags & (IRQF_NO_THREAD | IRQF_PERCPU | IRQF_ONESHOT)))
            lockdep_hardirq_threaded();
 
        trace_irq_handler_entry(irq, action);
        res = action->handler(irq, action->dev_id);  // 调用用户注册的中断服务
        trace_irq_handler_exit(irq, action, res);
 
        if (WARN_ONCE(!irqs_disabled(),"irq %u handler %pS enabled interrupts\n",
                  irq, action->handler))
            local_irq_disable();
 
        switch (res) {
        case IRQ_WAKE_THREAD:
            /*
             * Catch drivers which return WAKE_THREAD but
             * did not set up a thread function
             */
            if (unlikely(!action->thread_fn)) {
                warn_no_thread(irq, action);
                break;
            }
 
            __irq_wake_thread(desc, action);
 
            fallthrough;    /* to add to randomness */
        case IRQ_HANDLED:
            *flags |= action->flags;
            break;
 
        default:
            break;
        }
 
        retval |= res;
    }
 
    return retval;
}

  omap_gpio_probe 是如何将 irq_chip 关联到 irq_desc 结构体的, 也就是说  kernel调用 desc->handle_irq(desc) 时,怎么就能调用到 handle_simple_irq?后续继续分析!

  

posted @   迷途小菜鸟  阅读(44)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
点击右上角即可分享
微信分享提示