程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)

linux驱动移植-中断子系统执行流程

上一篇博客在最后,我们大致介绍了一下中断子系统的执行流程,这一节我们将从Linux源码层面去中断是如何原型。

一、裸机中断

我们首先回忆一下裸机程序中的中断流程是怎样的,以Mini2440按键K1外部中断为例

1、使能外部,开启外部中断EINTMASK 、中断源INTMSK 、开启IRQ总中断;

2、按键按下,触发中断源EINT8_23、CPU接收到中断信号;

3、CPU跳转到中断向量入口执行(0x18中断地址处);

  • 使用stmdb将寄存器值保存在栈顶(保护现场);
  • 执行中断处理函数;
  • 使用ldmia将栈顶处数据读出到寄存器中,并使pc=lr(恢复现场);

二、Linux中的中断处理

这一节我们将分析linux中CPU跳转到中断向量入口执行之后的流程:

  • 异常向量表0xFFFF0018中断地址处定义的vector_irq;
  • irq_handler中断处理函数的执行,在该中断处理函数中会根据IRQ编号找到绑定的我们自己的中断处理函数;

关于中断的注册(使能)、卸载(关闭)、以及每一个中断和对应中断处理函数的绑定会在后面小节一一介绍。

2.1 高端异常向量模式

实际上,ARM的异常向量表有两种选择,一种是低端向量,向量地址位于0x00000000,另一种是高端地址,向量地址0xffff0000,linux中选择使用高端向量模式,也就是说,当异常发生时,CPU会把PC指针自动跳转到始于0xffff0000开始的某一个地址上:

地址异常模式
FFFF0000 复位 管理模式(linux内核运行模式)
FFFF0004 未定义指令 未定义模式
FFFF0008 软中断(swi) 管理模式
FFFF000C Prefetch abort 终止模式
FFFF0010 Data abort 终止模式
FFFF0014 保留 保留
FFFF0018 IRQ 中断模式
FFFF001C FIQ 快中断模式

2.2 异常向量表

异常向量表在arch/arm/kernel/entry-armv.S中定义,为了方便讨论,下面只列出部分关键的代码:

复制代码
.L__vectors_start:
        W(b)    vector_rst
        W(b)    vector_und
        W(ldr)  pc, .L__vectors_start + 0x1000
        W(b)    vector_pabt
        W(b)    vector_dabt
        W(b)    vector_addrexcptn
        W(b)    vector_irq
        W(b)    vector_fiq
复制代码

__vectors_start是向量表基地址,在arch/arm/kernel/vmlinux.lds中定义了__vectors_start的链接地址为0xffff0000:

复制代码
 __vectors_start = .; 
.vectors 0xffff0000 : AT(__vectors_start) { 
    *(.vectors) 
} 
. = __vectors_start + SIZEOF(.vectors);
 __vectors_end = .;
__stubs_start = .; 
.stubs ADDR(.vectors) + 0x1000 : AT(__stubs_start) { 
    *(.stubs) 
} 
. = __stubs_start + SIZEOF(.stubs); 
__stubs_end = .; 
复制代码

这里定义两个段:

.vectors段:起始地址为__vectors_start(0xffff0000),结束地址为__vectors_end;

.stubs段:起始地址为__stubs_start(0xffff0000+0x1000) ,结束地址为__stubs_end ;

在链接脚本中定义的变量是可以直接在汇编代码中使用的,比如这里的__vectors_start、__vectors_end 等。

2.3 vector_sub宏

异常向量表中的vector_rst、vector_und等是通过vector_stub宏定义的:

复制代码
/*
 * Vector stubs.
 *
 * This code is copied to 0xffff1000 so we can use branches in the
 * vectors, rather than ldr's.  Note that this code must not exceed
 * a page size.
 *
 * Common stub entry macro:
 *   Enter in IRQ mode, spsr = SVC/USR CPSR, lr = SVC/USR PC
 *
 * SP points to a minimal amount of processor-private memory, the address
 * of which is copied into r0 for the mode specific abort handler.
 */
        .macro  vector_stub, name, mode, correction=0
        .align  5

vector_\name:                                                   @定义不同的宏 比如vector_irq
        .if \correction                                         @判断correction是否为0      
        sub     lr, lr, #\correction                            @计算返回地址
        .endif

        @
        @ Save r0, lr_<exception> (parent PC) and spsr_<exception>
        @ (parent CPSR)
        @
        stmia   sp, {r0, lr}            @ save r0, lr
        mrs     lr, spsr
        str     lr, [sp, #8]            @ save spsr

        @
        @ Prepare for SVC32 mode.  IRQs remain disabled.
        @
        mrs     r0, cpsr
        eor     r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE)
        msr     spsr_cxsf, r0

        @
        @ the branch table must immediately follow this code
        @
        and     lr, lr, #0x0f
 THUMB( adr     r0, 1f                  )
 THUMB( ldr     lr, [r0, lr, lsl #2]    )
        mov     r0, sp
 ARM(   ldr     lr, [pc, lr, lsl #2]    )
        movs    pc, lr                  @ branch to handler in SVC mode
ENDPROC(vector_\name)
.align 2
@ handler address follow this label
1:
.endm
复制代码

定义带有三个参数的宏vector_stub。比如下面语句:

 vector_stub     irq, IRQ_MODE, 4

宏展开后就变成了:

复制代码
vector_irq:
        sub     lr, lr, 4                     @计算返回地址  lr=保存的是进入中断前PC的值,就是发生中断所在的指令地址+8  这里减44,也就是正常执行的下一条指令地址

        @
        @ Save r0, lr_<exception> (parent PC) and spsr_<exception>
        @ (parent CPSR)
        @
        stmia   sp, {r0, lr}            @ save r0, lr   @保存r0、lr
        mrs     lr, spsr                @ lr=spsr 这里保存的是上一个模式下的cpsr的值
        @在保存现场时,如果处于svc模式下时,cpsr寄存器是写入irq模式下的spsr_irq寄存器,而不是svc模式下的spsr_svc,这样,在中断模式下恢复的话,
        @将spsr_irq寄存器里的内容写入cpsr,就能恢复到svc模式了,因为,spsr_irq寄存器里的内容就是svc模式下的状态
        str     lr, [sp, #8]            @ save spsr     @保存spsr

        @
        @ Prepare for SVC32 mode.  IRQs remain disabled.
        @
        mrs     r0, cpsr                                 @r0=cpsr
        eor     r0, r0, #(IRQ_MODE ^ SVC_MODE | PSR_ISETSTATE)  
        msr     spsr_cxsf, r0                            @修改当前模式下的spsr 写回

         @
         @ the branch table must immediately follow this code
         @
         and    lr, lr, #0x0f               @lr=lr&0x0f 获取进入irq模式前的cpsr的模式位
         mov  r0, sp
         ldr     lr, [pc, lr, lsl #2]       @如果进入irq前是usr,则lr=*(PC+0<<2),即__irq_usr, 如果进入irq前是svc,lr=*(PC+3<<2),即__irq_svc

         movs pc, lr                        @跳转到下面某处,且目标寄存器是pc,指令S结尾,最后会恢复cpsr.
     
         .long __irq_usr                              @  0  (USR_26 / USR_32)
         .long __irq_invalid                          @  1  (FIQ_26 / FIQ_32)

 


         .long __irq_invalid                          @  2  (IRQ_26 / IRQ_32)
         .long __irq_svc                              @  3  (SVC_26 / SVC_32)
         .long __irq_invalid                          @  4
         .long __irq_invalid                          @  5
         .long __irq_invalid                          @  6
         .long __irq_invalid                          @  7
         .long __irq_invalid                          @  8
         .long __irq_invalid                          @  9
         .long __irq_invalid                          @  a
         .long __irq_invalid                          @  b
         .long __irq_invalid                          @  c
         .long __irq_invalid                          @  d
         .long __irq_invalid                          @  e
         .long __irq_invalid                          @  f
复制代码

从上面代码中的注释可以看出:

  • 将发生异常前的各个寄存器值保存在SP栈里,若是中断异常,则PC=PC-4,也就是CPU下个要运行的位置处;
  • 然后根据进入中断前的工作模式不同,程序下一步将跳转到_irq_usr 、或__irq_svc等位置;

执行完后,栈空间信息如下:

2.4 irq_handler

对于系统的外部设备来说,通常都是使用IRQ中断,所以我们只关注__irq_usr和__irq_svc,两者的区别是进入和退出中断时是否进行用户栈和内核栈之间的切换,还有进程调度和抢占的处理等。

__irq_usr、__irq_svc实现均在arch/arm/kernel/entry-armv.S:

__irq_usr:
        usr_entry        @ 栈中保护中断上下文 r0~r12、sp、lr、pc、cpsr
        kuser_cmpxchg_check
        irq_handler
        get_thread_info tsk
        mov     why, #0
        b       ret_to_user_from_irq
复制代码
__irq_svc:
        svc_entry
        irq_handler

#ifdef CONFIG_PREEMPT                           @ 配置了抢占 
        ldr     r8, [tsk, #TI_PREEMPT]          @ get preempt count
        ldr     r0, [tsk, #TI_FLAGS]            @ get flags
        teq     r8, #0                          @ if preempt count != 0
        movne   r0, #0                          @ force flags to 0
        tst     r0, #_TIF_NEED_RESCHED
        blne    svc_preempt
#endif

        svc_exit r5, irq = 1                    @ return from exception
复制代码

两个函数最终都会进入irq_handler这个宏:

复制代码
        .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
9997:
        .endm
复制代码

如果选择了CONFIG_GENERIC_MULTI_IRQ_HANDLER配置项,则意味着允许平台的代码可以动态设置irq处理程序,平台代码可以修改全局变量:handle_arch_irq,从而可以修改irq的处理程序。

这里我们先讨论默认的实现:arch_irq_handler_default。

2.5 arch_irq_handler_default

arch_irq_handler_default它位于arch/arm/include/asm/entry-macro-multi.S中:

复制代码
/*
 * Interrupt handling.  Preserves r7, r8, r9
 */
        .macro  arch_irq_handler_default
        get_irqnr_preamble r6, lr
1:      get_irqnr_and_base r0, r2, r6, lr            @获取硬件中断号,r0=硬件中断号
        movne   r1, sp                               #r1=sp (栈中存放的寄存器上下文信息,对应结构体struct pt_regs) 
        @
        @ routine called with r0 = irq number, r1 = struct pt_regs *
        @
        badrne  lr, 1b
        bne     asm_do_IRQ                       

#ifdef CONFIG_SMP                                 @配置了多核
        /*
         * XXX
         *
         * this macro assumes that irqstat (r2) and base (r6) are
         * preserved from get_irqnr_and_base above
         */
        ALT_SMP(test_for_ipi r0, r2, r6, lr)
        ALT_UP_B(9997f)
        movne   r1, sp
        badrne  lr, 1b
        bne     do_IPI
#endif
9997:
        .endm
复制代码

get_irqnr_preamble和get_irqnr_and_base两个宏由machine级的代码定义,目的就是从中断控制器中获得硬件中断号,紧接着就调用asm_do_IRQ,从这个函数开始,中断程序进入C代码中,传入的参数是硬件中断号和寄存器结构指针,这个函数在arch/arm/kernel/irq.c中实现:

复制代码
/*
 * handle_IRQ handles all hardware IRQ's.  Decoded IRQs should
 * not come via this function.  Instead, they should provide their
 * own 'handler'.  Used by platform code implementing C-based 1st
 * level decoding.
 */
void handle_IRQ(unsigned int irq, struct pt_regs *regs)    // 参数1为硬件中断号,参数2寄存器结构指针,保存现场时的寄存器信息
{
        __handle_domain_irq(NULL, irq, false, regs);
}

/*
 * asm_do_IRQ is the interface to be used from assembly code.
 */
asmlinkage void __exception_irq_entry
asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
        handle_IRQ(irq, regs);
}
复制代码

到这里,中断程序完成了从asm代码到C代码的传递,并且获得了引起中断的硬件中断号。

2.6 __handle_domain_irq

__handle_domain_irq定义在文件kernel/irq/irqdesc.c中:

复制代码
/**
 * __handle_domain_irq - Invoke the handler for a HW irq belonging to a domain
 * @domain:     The domain where to perform the lookup
 * @hwirq:      The HW irq number to convert to a logical one
 * @lookup:     Whether to perform the domain lookup or not
 * @regs:       Register file coming from the low-level handling code
 *
 * Returns:     0 on success, or -EINVAL if conversion has failed
 */
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编号
#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_exit();               // 退出的时候会检查是否由软中断,如果有。则调用软中断
        set_irq_regs(old_regs);
        return ret;
}
复制代码

在函数内部首先调用set_irq_regs保存被中断的线程的上下文,线程的上下文被保存在old_regs中,以便中断退出之后可以恢复。

irq_enter()和irq_exit()函数分别用于标记generic handler的进入和退出。

generic_handle_irq是通用逻辑层提供的API,通过该API,中断的控制被传递到了与体系结构无关的中断流控层:

复制代码
int generic_handle_irq(unsigned int irq)
{
    struct irq_desc *desc = irq_to_desc(irq);   // 通过IRQ编号获取中断描述符
 
    if (!desc)
        return -EINVAL;
    generic_handle_irq_desc(irq, desc);
    return 0;
}
复制代码

最终会进入该irq中断描述符的流控处理回调中:

static inline void generic_handle_irq_desc(unsigned int irq, struct irq_desc *desc)
{
    desc->handle_irq(irq, desc);
}

handle_irq使用chip结构中的函数清除、屏蔽或者重新使能中断,还要调用用户在action链表中注册的中断处理函数。

三、内核启动中断初始化

3.1 start_kernel

我们之前介绍到u-boot将linux内核加载到内存空间后,会跳转到内核执行,内核执行的起始代码实际上是start_kernel函数。

  • 首先,在setup_arch函数中,early_trap_init被调用,主要进行异常向量表和异常处理函数的的代码重定位工作;
  • 然后,start_kernel发出early_irq_init调用,early_irq_init属于与硬件和平台无关的通用逻辑层,它完成irq_desc结构的内存申请,为它们其中某些字段填充默认值,完成后调用体系相关的arch_early_irq_init函数完成进一步的初始化工作,不过ARM体系没有实现arch_early_irq_init;
  • 接着,start_kernel发出init_IRQ调用,它会直接调用所属板子machine_desc结构体中的init_irq回调。machine_desc通常在板子的特定代码中,使用MACHINE_START和MACHINE_END宏进行定义;
  • machine_desc->init_irq完成对中断控制器的初始化,为每个irq_desc结构安装合适的流控handler,为每个irq_desc结构安装irq_chip指针,使他指向正确的中断控制器所对应的irq_chip结构的实例,同时,如果该平台中的中断线有多路复用(多个中断公用一个irq中断线)的情况,还应该初始化irq_desc中相应的字段和标志,以便实现中断控制器的级联;

3.1 初始化异常向量表

early_trap_init位于arch/arm/kernel/traps.c:

复制代码
void __init early_trap_init(void *vectors_base)
{
#ifndef CONFIG_CPU_V7M
        unsigned long vectors = (unsigned long)vectors_base;
        extern char __stubs_start[], __stubs_end[];
        extern char __vectors_start[], __vectors_end[];
        unsigned i;

        vectors_page = vectors_base;

        /*
         * Poison the vectors page with an undefined instruction.  This
         * instruction is chosen to be undefined for both ARM and Thumb
         * ISAs.  The Thumb version is an undefined instruction with a
         * branch back to the undefined instruction.
         */
        for (i = 0; i < PAGE_SIZE / sizeof(u32); i++)
                ((u32 *)vectors_base)[i] = 0xe7fddef1;

        /*
         * Copy the vectors, stubs and kuser helpers (in entry-armv.S)
         * into the vector page, mapped at 0xffff0000, and ensure these
         * are visible to the instruction stream.
         */
        memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
        memcpy((void *)vectors + 0x1000, __stubs_start, __stubs_end - __stubs_start);

        kuser_init(vectors_base);

        flush_icache_range(vectors, vectors + PAGE_SIZE * 2);
#else /* ifndef CONFIG_CPU_V7M */
        /*
         * on V7-M there is no need to copy the vector table to a dedicated
         * memory area. The address is configurable and so a table in the kernel
         * image can be used.
         */
#endif
}
复制代码

以上两个memcpy会把__vectors_start开始的代码拷贝到0xffff0000处,也就是.vectors段的内容,即异常向量表的内容;

把__stubs_start开始的代码拷贝到0xffff0000+0x1000处,也就是.stubs段的内容,也就是处理跳转的部分;

这样,异常到来时,CPU就可以正确地跳转到相应异常向量入口并执行他们。

3.2  基于数组方式分配中断描述符

early_irq_init位于kernel/irq/irqdesc.c文件:

复制代码
int __init early_irq_init(void)
{
        int count, i, node = first_online_node;
        struct irq_desc *desc;

        init_irq_default_affinity();

        printk(KERN_INFO "NR_IRQS: %d\n", NR_IRQS);   // 输出IRQ数量

        desc = irq_desc;
        count = ARRAY_SIZE(irq_desc);                 // 中断描述符数组大小 

        for (i = 0; i < count; i++) {                 // 遍历每一个中断描述符、初始化一些成员
                desc[i].kstat_irqs = alloc_percpu(unsigned int);  // 分配per-CPU变量
                alloc_masks(&desc[i], node);
                raw_spin_lock_init(&desc[i].lock);                   // 自旋锁初始化
                lockdep_set_class(&desc[i].lock, &irq_desc_lock_class);
                mutex_init(&desc[i].request_mutex);                  // 互斥锁初始化
                desc_set_defaults(i, &desc[i], node, NULL, NULL);
        }
        return arch_early_irq_init();   // 抵用arch相关的初始化函数,这里为空
}
复制代码

内核默认采用基于数组的方式来创建irq_desc数组(kernel/irq/irqdesc.c):

struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {  // 分配irq_desc
    [0 ... NR_IRQS-1] = {
        .handle_irq    = handle_bad_irq,
        .depth        = 1,
        .lock        = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
    }
};

还记的之前将linux内核移植到Mini2440开发板启动时的输出信息么:

NR_IRQS: 111
S3C2440: IRQ Support
irq: clearing pending status 00000002

可以发现NR_IRQS数量为111,定义在arch/arm/mach-s3c24xx/include/mach/irqs.h:

#define IRQ_S3C2416_I2S1        S3C2416_IRQ(7)        // 7 + 58 + 29 + 16 = 110

#if defined(CONFIG_CPU_S3C2416)     // 24xx系列 定义了这个
#define NR_IRQS (IRQ_S3C2416_I2S1 + 1)     // 111
#else
#define NR_IRQS (IRQ_S3C2443_AC97 + 1)
#endif

注意:

如果配置了CONFIG_SPARSE_IRQ,内核使用基数树(radix tree)来动态分配irq_desc结构:

复制代码
int __init early_irq_init(void)
{
        int i, initcnt, node = first_online_node;
        struct irq_desc *desc;

        init_irq_default_affinity();

        /* Let arch update nr_irqs and return the nr of preallocated irqs */
        initcnt = arch_probe_nr_irqs();                     // 体系结构相关的代码来决定预先分配的中断描述符的个数
        printk(KERN_INFO "NR_IRQS: %d, nr_irqs: %d, preallocated irqs: %d\n",
               NR_IRQS, nr_irqs, initcnt);

        if (WARN_ON(nr_irqs > IRQ_BITMAP_BITS))
                nr_irqs = IRQ_BITMAP_BITS;

        if (WARN_ON(initcnt > IRQ_BITMAP_BITS))
                initcnt = IRQ_BITMAP_BITS;

        if (initcnt > nr_irqs)       // initctn是需要在初始化的时候预分配的IRQ的个数,nr_irqs是当前系统中IR编号的最大值
                nr_irqs = initcnt;

        for (i = 0; i < initcnt; i++) {
                desc = alloc_desc(i, node, 0, NULL, NULL);   // 动态分配irq_desc
                set_bit(i, allocated_irqs);
                irq_insert_desc(i, desc);
        }
        return arch_early_irq_init();
}
View Code
复制代码

early_irq_init用于动态申请irq_desc结构的内存空间,具体分配多少个中断描述符通过arch_probe_nr_irqs得到的:

int __init arch_probe_nr_irqs(void)
{
    nr_irqs = machine_desc->nr_irqs ? machine_desc->nr_irqs : NR_IRQS;
    return nr_irqs;
}

3.3 初始化中断描述符

init_IRQ在文件arch/arm/kernel/irq.c定义:

复制代码
void __init init_IRQ(void)
{
        int ret;

        if (IS_ENABLED(CONFIG_OF) && !machine_desc->init_irq)
                irqchip_init();
        else
                machine_desc->init_irq();   // 执行

        if (IS_ENABLED(CONFIG_OF) && IS_ENABLED(CONFIG_CACHE_L2X0) &&
            (machine_desc->l2c_aux_mask || machine_desc->l2c_aux_val)) {
                if (!outer_cache.write_sec)
                        outer_cache.write_sec = machine_desc->l2c_write_sec;
                ret = l2x0_of_init(machine_desc->l2c_aux_val,
                                   machine_desc->l2c_aux_mask);
                if (ret && ret != -ENODEV)
                        pr_err("L2C: failed to init: %d\n", ret);
        }

        uniphier_cache_init();
}
复制代码

这里我们没有使用device tree,但是这边定义了machine_desc->init_irq,因此这里直接调用所属板子machine_desc结构体中的init_irq回调。

3.3.1 machine desc

machine_desc通常在板子的特定代码中,使用MACHINE_START和MACHINE_END宏进行定义。

比如在arch/arm/mach-s3c24xx/mach-smdk2440.c文件中定义:

复制代码
MACHINE_START(S3C2440, "SMDK2440")
        /* Maintainer: Ben Dooks <ben-linux@fluff.org> */
        .atag_offset    = 0x100,

        .init_irq       = s3c2440_init_irq,
        .map_io         = smdk2440_map_io,
        .init_machine   = smdk2440_machine_init,
        .init_time      = smdk2440_init_time,
MACHINE_END
复制代码

struct machine_desc ,这个结构体在内核移植中起到相当重要的作用,内核通过machine_desc结构体来控制系统体系架构相关部分的初始化。具体可以参考博客MACHINE_START与MACHINE_END

3.3.2 s3c2440_init_irq

由于machine_desc->init_irq初始化为s3c2440_init_irq,定位到文件drivers/irqchip/irq-s3c24xx.c:

复制代码
void __init s3c2440_init_irq(void)
{
        pr_info("S3C2440: IRQ Support\n");

#ifdef CONFIG_FIQ
        init_FIQ(FIQ_START);
#endif

        s3c_intc[0] = s3c24xx_init_intc(NULL, &init_s3c2440base[0], NULL,    // 为主中断控制器分配一个s3c_irq_intc
                                        0x4a000000);
        if (IS_ERR(s3c_intc[0])) {
                pr_err("irq: could not create main interrupt controller\n");
                return;
        }

        s3c24xx_init_intc(NULL, &init_eint[0], s3c_intc[0], 0x560000a4);      // 为外部子中断控制器分配一个s3c_irq_intc,其父为主中断控制器
        s3c_intc[1] = s3c24xx_init_intc(NULL, &init_s3c2440subint[0],         // 为内部子中断控制器分配一个s3c_irq_intc,其父为主中断控制器
                                        s3c_intc[0], 0x4a000018);
}
复制代码

在前已经动态分配了中断描述符,这里将会调用s3c24xx_init_intc进行中断控制器的初始化工作,包括主中断控制器、内部子中断控制器、外部子中断控制器。

我们下面以这行代码为例讲解:

     s3c_intc[0] = s3c24xx_init_intc(NULL, &init_s3c2440base[0], NULL,
                                        0x4a000000);                // 源挂起寄存器  SRCPND

其中init_s3c2440base、init_s3c2440subint、init_eint这些数组是用来描述S3C2440芯片的60个中断源的,这些中断又分为三类:

  • 主中断32个;
  • 内部子中断15个;
  • 外部中断24个;
复制代码
static struct s3c_irq_data init_s3c2440base[32] = {   // 对应S3C2440的32个主中断源
        { .type = S3C_IRQTYPE_EINT, }, /* EINT0 */
        { .type = S3C_IRQTYPE_EINT, }, /* EINT1 */
        { .type = S3C_IRQTYPE_EINT, }, /* EINT2 */
        { .type = S3C_IRQTYPE_EINT, }, /* EINT3 */
        { .type = S3C_IRQTYPE_LEVEL, }, /* EINT4to7 */
        { .type = S3C_IRQTYPE_LEVEL, }, /* EINT8to23 */
        { .type = S3C_IRQTYPE_LEVEL, }, /* CAM */
        { .type = S3C_IRQTYPE_EDGE, }, /* nBATT_FLT */
        { .type = S3C_IRQTYPE_EDGE, }, /* TICK */
        { .type = S3C_IRQTYPE_LEVEL, }, /* WDT/AC97 */
        { .type = S3C_IRQTYPE_EDGE, }, /* TIMER0 */
        { .type = S3C_IRQTYPE_EDGE, }, /* TIMER1 */
        { .type = S3C_IRQTYPE_EDGE, }, /* TIMER2 */
        { .type = S3C_IRQTYPE_EDGE, }, /* TIMER3 */
        { .type = S3C_IRQTYPE_EDGE, }, /* TIMER4 */
        { .type = S3C_IRQTYPE_LEVEL, }, /* UART2 */
        { .type = S3C_IRQTYPE_EDGE, }, /* LCD */
        { .type = S3C_IRQTYPE_EDGE, }, /* DMA0 */
        { .type = S3C_IRQTYPE_EDGE, }, /* DMA1 */
        { .type = S3C_IRQTYPE_EDGE, }, /* DMA2 */
        { .type = S3C_IRQTYPE_EDGE, }, /* DMA3 */
        { .type = S3C_IRQTYPE_EDGE, }, /* SDI */
        { .type = S3C_IRQTYPE_EDGE, }, /* SPI0 */
        { .type = S3C_IRQTYPE_LEVEL, }, /* UART1 */
        { .type = S3C_IRQTYPE_LEVEL, }, /* NFCON */
        { .type = S3C_IRQTYPE_EDGE, }, /* USBD */
        { .type = S3C_IRQTYPE_EDGE, }, /* USBH */
        { .type = S3C_IRQTYPE_EDGE, }, /* IIC */
        { .type = S3C_IRQTYPE_LEVEL, }, /* UART0 */
        { .type = S3C_IRQTYPE_EDGE, }, /* SPI1 */
        { .type = S3C_IRQTYPE_EDGE, }, /* RTC */
        { .type = S3C_IRQTYPE_LEVEL, }, /* ADCPARENT */
};

static struct s3c_irq_data init_s3c2440subint[32] = {    // S3C2440带有子中断的内部中断 一共15个中断源   对应6个主中断源
        { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 28 }, /* UART0-RX */
        { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 28 }, /* UART0-TX */
        { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 28 }, /* UART0-ERR */
        { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 23 }, /* UART1-RX */
        { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 23 }, /* UART1-TX */
        { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 23 }, /* UART1-ERR */
        { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 15 }, /* UART2-RX */
        { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 15 }, /* UART2-TX */
        { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 15 }, /* UART2-ERR */
        { .type = S3C_IRQTYPE_EDGE, .parent_irq = 31 }, /* TC */
        { .type = S3C_IRQTYPE_EDGE, .parent_irq = 31 }, /* ADC */
        { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 6 }, /* CAM_C */
        { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 6 }, /* CAM_P */
        { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 9 }, /* WDT */
        { .type = S3C_IRQTYPE_LEVEL, .parent_irq = 9 }, /* AC97 */
};
static struct s3c_irq_data __maybe_unused init_eint[32] = {     // 24个外部中断源 对应5个主中断源
        { .type = S3C_IRQTYPE_NONE, }, /* reserved */
        { .type = S3C_IRQTYPE_NONE, }, /* reserved */
        { .type = S3C_IRQTYPE_NONE, }, /* reserved */
        { .type = S3C_IRQTYPE_NONE, }, /* reserved */
        { .type = S3C_IRQTYPE_EINT, .parent_irq = 4 }, /* EINT4 */
        { .type = S3C_IRQTYPE_EINT, .parent_irq = 4 }, /* EINT5 */
        { .type = S3C_IRQTYPE_EINT, .parent_irq = 4 }, /* EINT6 */
        { .type = S3C_IRQTYPE_EINT, .parent_irq = 4 }, /* EINT7 */
        { .type = S3C_IRQTYPE_EINT, .parent_irq = 5 }, /* EINT8 */
        { .type = S3C_IRQTYPE_EINT, .parent_irq = 5 }, /* EINT9 */
        { .type = S3C_IRQTYPE_EINT, .parent_irq = 5 }, /* EINT10 */
        { .type = S3C_IRQTYPE_EINT, .parent_irq = 5 }, /* EINT11 */
        { .type = S3C_IRQTYPE_EINT, .parent_irq = 5 }, /* EINT12 */
        { .type = S3C_IRQTYPE_EINT, .parent_irq = 5 }, /* EINT13 */
        { .type = S3C_IRQTYPE_EINT, .parent_irq = 5 }, /* EINT14 */
        { .type = S3C_IRQTYPE_EINT, .parent_irq = 5 }, /* EINT15 */
        { .type = S3C_IRQTYPE_EINT, .parent_irq = 5 }, /* EINT16 */
        { .type = S3C_IRQTYPE_EINT, .parent_irq = 5 }, /* EINT17 */
        { .type = S3C_IRQTYPE_EINT, .parent_irq = 5 }, /* EINT18 */
        { .type = S3C_IRQTYPE_EINT, .parent_irq = 5 }, /* EINT19 */
        { .type = S3C_IRQTYPE_EINT, .parent_irq = 5 }, /* EINT20 */
        { .type = S3C_IRQTYPE_EINT, .parent_irq = 5 }, /* EINT21 */
        { .type = S3C_IRQTYPE_EINT, .parent_irq = 5 }, /* EINT22 */
        { .type = S3C_IRQTYPE_EINT, .parent_irq = 5 }, /* EINT23 */
};
View Code
复制代码

我们顺便看一下sec_irq_data、s3c_irq_init的结构:

复制代码
struct s3c_irq_data {        // 用于保存中断源信息
        unsigned int type;
        unsigned long offset;
        unsigned long parent_irq;           // 如果是子中断,这里保存子中断对应的主中断硬件编号

        /* data gets filled during init */
        struct s3c_irq_intc *intc;           // 存放中断控制器信息
        unsigned long sub_bits;              // 如果当前是主中断源,并且有多个子中断,那么子中断对应的硬件编号为置 1
        struct s3c_irq_intc *sub_intc;       // 子中断中断控制器信息
};

/*
 * Structure holding the controller data
 * @reg_pending         register holding pending irqs
 * @reg_intpnd          special register intpnd in main intc
 * @reg_mask            mask register
 * @domain              irq_domain of the controller
 * @parent              parent controller for ext and sub irqs
 * @irqs                irq-data, always s3c_irq_data[32]
 */
struct s3c_irq_intc {                                   // 保存中断控制器数据
        void __iomem            *reg_pending;           // 指定源挂起寄存器
        void __iomem            *reg_intpnd;            // 指定中断挂起寄存器
        void __iomem            *reg_mask;              // 指定中断屏蔽寄存器
        struct irq_domain       *domain;
        struct s3c_irq_intc     *parent;
        struct s3c_irq_data     *irqs;
};

/*
 * Array holding pointers to the global controller structs
 * [0] ... main_intc
 * [1] ... sub_intc
 * [2] ... main_intc2 on s3c2416
 */
static struct s3c_irq_intc *s3c_intc[3];
复制代码

这里使用s3c_irq_init保存中断控制器的信息,比如寄存器信息、中断域、以及当前中断控制器所控制的中断源。

我们后面也将这个结构称为中断控制器,这个结构不同于irq_chip(irq_chip保存的主要是关中断、开中断、屏蔽中断等回调函数)。

实际上S3C2440物理只有一个中断控制器,这里软件抽象出来3个中断控制器:

  • 一个根中断控制器管理主中断源;
  • 两个子中断控制器,一个用于管理外部中断源、另一个管理内部子中断源;

为了方便理解,我们将这三个中断控制器以级联的方式展示:

3.4 s3c24xx_init_intc

我们继续看一下s3c24xx_init_intc函数,该函数位于drivers/irqchip/irq-s3c24xx.c:

复制代码
static struct s3c_irq_intc * __init s3c24xx_init_intc(struct device_node *np,
                                       struct s3c_irq_data *irq_data, // 中断源信息数组 
                                       struct s3c_irq_intc *parent,   // 指定父中断控制器
                                       unsigned long address)         // 指定中断控制寄存器基地址
{
        struct s3c_irq_intc *intc;
        void __iomem *base = (void *)0xf6000000; /* static mapping */
        int irq_num;
        int irq_start;
        int ret;

        intc = kzalloc(sizeof(struct s3c_irq_intc), GFP_KERNEL);   // 动态分配src_irq_init结构
        if (!intc)
                return ERR_PTR(-ENOMEM);

        intc->irqs = irq_data;

        if (parent)
                intc->parent = parent;                                 // 设置父中断控制器  

        /* select the correct data for the controller.
         * Need to hard code the irq num start and offset
         * to preserve the static mapping for now    
* 根据实际物理地址,配置MMU映射后的中断相关寄存器地址
*/ switch (address) { case 0x4a000000: // SRCPND 源挂起寄存器 pr_debug("irq: found main intc\n"); intc->reg_pending = base; // 指定SRCPND intc->reg_mask = base + 0x08; // 指定INTMASK intc->reg_intpnd = base + 0x10; // 指定INTPND irq_num = 32; // 中断源数量 irq_start = S3C2410_IRQ(0); // S3C2410_IRQ定义为((X) + S3C2410_CPUIRQ_OFFSET) 最终等于0+16=16 break; case 0x4a000018: // SUBSRCPND 次级源挂起寄存器 pr_debug("irq: found subintc\n"); intc->reg_pending = base + 0x18; // 指定SUBSRCPND intc->reg_mask = base + 0x1c; // 指定INTSUBMSK irq_num = 29; // 中断源数量 为啥是29? irq_start = S3C2410_IRQSUB(0); // 0+ 16 + 58 = 74 break; case 0x4a000040: pr_debug("irq: found intc2\n"); intc->reg_pending = base + 0x40; intc->reg_mask = base + 0x48; intc->reg_intpnd = base + 0x50; irq_num = 8; irq_start = S3C2416_IRQ(0); break; case 0x560000a4: // EINTMASK 外部中断屏蔽寄存器 pr_debug("irq: found eintc\n"); base = (void *)0xfd000000; intc->reg_mask = base + 0xa4; // EINTMASK intc->reg_pending = base + 0xa8; // EINTPND irq_num = 24; // 中断源数量 irq_start = S3C2410_IRQ(32); // 32+16=48 break; default: pr_err("irq: unsupported controller address\n"); ret = -EINVAL; goto err; } /* now that all the data is complete, init the irq-domain */ s3c24xx_clear_intc(intc); // 清除中断挂起 intc->domain = irq_domain_add_legacy(np, irq_num, irq_start, // 初始化中断域,中断域存储了硬件中断号到IRQ编号的映射 irq_start为当前中断域对应的IRQ起始编号 0, &s3c24xx_irq_ops, intc); if (!intc->domain) { pr_err("irq: could not create irq-domain\n"); ret = -EINVAL; goto err; } set_handle_irq(s3c24xx_handle_irq); // 设置irq总中断入口程序为s3c24xx_handle_irq return intc; err: kfree(intc); return ERR_PTR(ret); }
复制代码

这个函数主要做了以下事情:

  • 为每一个中断控制器动态分配s3c_irq_intc;
  • 初始化s3c_irq_intc中断相关寄存器:源挂起寄存器、中断屏蔽寄存器、中断挂起寄存器;
  • 清除中断挂起状态;
  • 为每一个中断控制器分配irq_domain,并进行初始化:
    • 追加到全局链表irq_domain_list;
    • 执行domain->ops->map(domain, virq, hwirq),设置中断流控处理函数以及irq_chip;
    • 创建硬件中断号到IRQ编号的映射;
  • 设置中断统一入口程序为s3c24xx_handle_irq;

代码执行完之后s3c_intc结构大致如下:

四、s3c24xx_init_intc代码分析

由于s3c24xx_init_intc里面主要是s3c2440处理器中断相关的初始化代码,并且内容比较多,且比较重要,因此我们单独进行分析每一块内容。

4.1 s3c24xx_clear_intc

 其中s3c24xx_clear_intc通过向INTPND或者EINTPND相应位写1来清除中断的挂起状态:

复制代码
static void s3c24xx_clear_intc(struct s3c_irq_intc *intc)
{
        void __iomem *reg_source;
        unsigned long pend;
        unsigned long last;
        int i;

        /* if intpnd is set, read the next pending irq from there */
        reg_source = intc->reg_intpnd ? intc->reg_intpnd : intc->reg_pending;  // 中断挂起期存器?中断挂起期存器:源挂起寄存器

        last = 0;
        for (i = 0; i < 4; i++) {
                pend = readl_relaxed(reg_source);    // 读取寄存器的值

                if (pend == 0 || pend == last)
                        break;

                writel_relaxed(pend, intc->reg_pending);   // 写1、清除挂起状态
                if (intc->reg_intpnd)
                        writel_relaxed(pend, intc->reg_intpnd);

                pr_info("irq: clearing pending status %08x\n", (int)pend);
                last = pend;
        }
}
复制代码

4.2 创建并初始化中断域

intc->domain = irq_domain_add_legacy(np, irq_num, irq_start,
                                             0, &s3c24xx_irq_ops,
                                             intc);

其中参数s3c24xx_irq_ops类型为irq_domain_ops,这个我们在上一篇博客中介绍过:

static const struct irq_domain_ops s3c24xx_irq_ops = {
        .map = s3c24xx_irq_map,          // 比较重要  s3c24xx_irq_map函数主要工作就是设置每个中断的流控处理函数   单独介绍
        .xlate = irq_domain_xlate_twocell,
};

irq_domain_add_legacy函数在kernel/irq/irqdomain.c中定义,该函数用于分配struct irq_domain,这里通过host_data字段绑定了中断控制器s3c_irq_intc和中断域的关系,最后将创建好的struct irq_domain添加到全局链表irq_domain_list:

复制代码
/**
 * irq_domain_add_legacy() - Allocate and register a legacy revmap irq_domain.
 * @of_node: pointer to interrupt controller's device tree node.
 * @size: total number of irqs in legacy mapping
 * @first_irq: first number of irq block assigned to the domain
 * @first_hwirq: first hwirq number to use for the translation. Should normally
 *               be '0', but a positive integer can be used if the effective
 *               hwirqs numbering does not begin at zero.
 * @ops: map/unmap domain callbacks
 * @host_data: Controller private data pointer
 *
 * Note: the map() callback will be called before this function returns
 * for all legacy interrupts except 0 (which is always the invalid irq for
 * a legacy controller).
 */
struct irq_domain *irq_domain_add_legacy(struct device_node *of_node,
                                         unsigned int size,            //中断数量 以主中断为例 32
                                         unsigned int first_irq,       //起始IRQ编号  16
                                         irq_hw_number_t first_hwirq,  //起始硬件中断号   0
                                         const struct irq_domain_ops *ops,
                                         void *host_data)              // 可以传递与中断域相关的任何主机数据,用于设置中断域的host_data私有字段 
{
        struct irq_domain *domain;

        domain = __irq_domain_add(of_node_to_fwnode(of_node), first_hwirq + size,   // 创建irq_domain
                                  first_hwirq + size, 0, ops, host_data);
        if (domain)
                irq_domain_associate_many(domain, first_irq, first_hwirq, size);    // 创建映射

        return domain;
}
复制代码

需要注意的是irq_domain_add_legacy并不是标准的注册irq domain的接口函数,标准函数有irq_domain_add_linear、irq_domain_add_tree、irq_domain_add_nomap,当然这些函数内部都是调用的__irq_domain_add。

4.2.1 创建中断域

__irq_domain_add用于创建一个通用中断域结构体并返回其指针。定义如下:

复制代码
/**
 * __irq_domain_add() - Allocate a new irq_domain data structure
 * @fwnode: firmware node for the interrupt controller
 * @size: Size of linear map; 0 for radix mapping only
 * @hwirq_max: Maximum number of interrupts supported by controller
 * @direct_max: Maximum value of direct maps; Use ~0 for no limit; 0 for no
 *              direct mapping
 * @ops: domain callbacks
 * @host_data: Controller private data pointer
 *
 * Allocates and initialize and irq_domain structure.
 * Returns pointer to IRQ domain, or NULL on failure.
 */
struct irq_domain *__irq_domain_add(struct fwnode_handle *fwnode, int size,     // size为线性映射表大小 0+32=32
                                    irq_hw_number_t hwirq_max, int direct_max,  // hwiq_max硬件中断号最大值 0+32=32
                                    const struct irq_domain_ops *ops,           // 该结构存放大量函数指针
                                    void *host_data)                             //中断控制器    
{
        struct device_node *of_node = to_of_node(fwnode);
        struct irqchip_fwid *fwid;
        struct irq_domain *domain;

        static atomic_t unknown_domains;

        domain = kzalloc_node(sizeof(*domain) + (sizeof(unsigned int) * size), // 动态分配struct irq_domain、最后4*size是线性lookup table表大小
                              GFP_KERNEL, of_node_to_nid(of_node));
        if (WARN_ON(!domain))
                return NULL;

        if (fwnode && is_fwnode_irqchip(fwnode)) {
                fwid = container_of(fwnode, struct irqchip_fwid, fwnode);

                switch (fwid->type) {
                case IRQCHIP_FWNODE_NAMED:
                case IRQCHIP_FWNODE_NAMED_ID:
                        domain->name = kstrdup(fwid->name, GFP_KERNEL);
                        if (!domain->name) {
                                kfree(domain);
                                return NULL;
                        }
                        domain->flags |= IRQ_DOMAIN_NAME_ALLOCATED;
                        break;
                default:
                        domain->fwnode = fwnode;
                        domain->name = fwid->name;
                        break;
                }
        } else if (of_node) {
                char *name;

                /*
                 * DT paths contain '/', which debugfs is legitimately
                 * unhappy about. Replace them with ':', which does
                 * the trick and is not as offensive as '\'...
                 */
                name = kasprintf(GFP_KERNEL, "%pOF", of_node);
                if (!name) {
                        kfree(domain);
                        return NULL;
                }

                strreplace(name, '/', ':');

                domain->name = name;
                domain->fwnode = fwnode;
                domain->flags |= IRQ_DOMAIN_NAME_ALLOCATED;
        }

        if (!domain->name) {
                if (fwnode)
                        pr_err("Invalid fwnode type for irqdomain\n");
                domain->name = kasprintf(GFP_KERNEL, "unknown-%d",
                                         atomic_inc_return(&unknown_domains));
                if (!domain->name) {
                        kfree(domain);
                        return NULL;
                }
                domain->flags |= IRQ_DOMAIN_NAME_ALLOCATED;
        }

        of_node_get(of_node);
        /* Fill structure */
        INIT_RADIX_TREE(&domain->revmap_tree, GFP_KERNEL);
        mutex_init(&domain->revmap_tree_mutex);
        domain->ops = ops;                     // 设置操作集,用于创建映射、翻译等 
        domain->host_data = host_data;         // 绑定中断控制器
        domain->hwirq_max = hwirq_max;        // 设置硬件中断号最大值
        domain->revmap_size = size;           // 设置查找表lookup table大小 
        domain->revmap_direct_max_irq = direct_max;
        irq_domain_check_hierarchy(domain);   // 检查中断域是否级联

        mutex_lock(&irq_domain_mutex);
        debugfs_add_domain_dir(domain);
        list_add(&domain->link, &irq_domain_list);   // 添加到连表,以便发生中断时能能够找到对应的中断域
        mutex_unlock(&irq_domain_mutex);

        pr_debug("Added domain %s\n", domain->name);
        return domain;
}
复制代码

该函数接受 6 个参数:

  • fwnode:表示与中断域相关联的固件节点(Firmware Node);
  • size:新建中断域所需的中断号数量;
  • hwirq_max:硬件中断号的最大值,一般等于size-1;
  • direct_max:直接中断号的最大值,通常设置为0;
  • ops:一个指向 irq_domain_ops 结构体的指针,该结构体定义了中断域的行为。
  • host_data:一个指针,可以传递与中断域相关的任何主机数据,用于设置中断域的host_data私有字段;;

调用该函数之后,如果成功,将返回一个指向新创建中断域的指针。

该函数适用于需要更复杂的中断管理、或支持不同种类的中断控制器的场景。比如,一个 SoC 中可以有多个中断控制器,每个中断控制器都需要一个中断域管理其所管辖的硬件设备的中断号,这时就可以使用 __irq_domain_add创建一个中断域并将其与对应的中断控制器关联。

4.2.2 创建中断映射

创建中断域以后,需要向中断域添加硬件中断号到IRQ编号的映射,这里调用了irq_domain_associate_many函数来实现:

复制代码
void irq_domain_associate_many(struct irq_domain *domain, unsigned int irq_base,
                               irq_hw_number_t hwirq_base, int count)
{
        struct device_node *of_node;
        int i;

        of_node = irq_domain_get_of_node(domain);
        pr_debug("%s(%s, irqbase=%i, hwbase=%i, count=%i)\n", __func__,
                of_node_full_name(of_node), irq_base, (int)hwirq_base, count);

        // 遍历中断,在domain线性表,其实就是lookup table,建立硬件中断号到IRQ编号的映射,硬件中断号作为index,通过查表可以获取对应的IRQ编号。
        for (i = 0; i < count; i++) {
                irq_domain_associate(domain, irq_base + i, hwirq_base + i);
        }
}
复制代码

第一个参数为中断域,第二个参数为起始IRQ编号,第三个参为起始硬件中断号,最后一个参数为中断数。

内部调用了irq_domain_associate函数:

复制代码
int irq_domain_associate(struct irq_domain *domain, unsigned int virq,
                         irq_hw_number_t hwirq)
{
        // 根据IRQ编号获取中断数据
        struct irq_data *irq_data = irq_get_irq_data(virq);
        int ret;

        if (WARN(hwirq >= domain->hwirq_max,
                 "error: hwirq 0x%x is too large for %s\n", (int)hwirq, domain->name))
                return -EINVAL;
        if (WARN(!irq_data, "error: virq%i is not allocated", virq))
                return -EINVAL;
        if (WARN(irq_data->domain, "error: virq%i is already associated", virq))
                return -EINVAL;

        mutex_lock(&irq_domain_mutex);
        irq_data->hwirq = hwirq;                     // 设置硬件中断号
        irq_data->domain = domain;                   // 设置中断域  
        if (domain->ops->map) {
                // 调用irq domain的map callback函数,即s3c24xx_irq_map
                ret = domain->ops->map(domain, virq, hwirq);
                if (ret != 0) {
                        /*
                         * If map() returns -EPERM, this interrupt is protected
                         * by the firmware or some other service and shall not
                         * be mapped. Don't bother telling the user about it.
                         */
                        if (ret != -EPERM) {
                                pr_info("%s didn't like hwirq-0x%lx to VIRQ%i mapping (rc=%d)\n",
                                       domain->name, hwirq, virq, ret);
                        }
                        irq_data->domain = NULL;
                        irq_data->hwirq = 0;
                        mutex_unlock(&irq_domain_mutex);
                        return ret;
                }

                /* If not already assigned, give the domain the chip's name */
                if (!domain->name && irq_data->chip)
                        domain->name = irq_data->chip->name;
        }

        domain->mapcount++;
        irq_domain_set_mapping(domain, hwirq, irq_data);  // 把硬件中断号到IRQ编号的映射添加到中断域的线性表linear_revmap
        mutex_unlock(&irq_domain_mutex);

        irq_clear_status_flags(virq, IRQ_NOREQUEST);   // 该中断已经可以申请了,因此clear相关flag

        return 0;
}
复制代码

第一个参数为中断域,第二个参数为IRQ编号,第三个参为硬件中断号。重点流程:

  • 在该函数内部回调了domain->ops->map(domain, virq, hwirq)。对于给定的映射,该函数只被调用一次,它通常使用irq_set_chip_and_handler 将IRQ编号与指定的处理程序进行映射,这样调用generic_handle_irq将触发正确的处理程序;
  • 此外该函数调用irq_domain_set_mapping把硬件中断号到IRQ编号的映射添加到中断域的线性表linear_revmap中;

irq_domain_set_mapping定义如下:

复制代码
static void irq_domain_set_mapping(struct irq_domain *domain,
                                   irq_hw_number_t hwirq,
                                   struct irq_data *irq_data)
{
        if (hwirq < domain->revmap_size) {     // 硬件中断号 < 线性lookup table的大小
                domain->linear_revmap[hwirq] = irq_data->irq;     // 填写线性映射lookup table的数据
        } else {
                mutex_lock(&domain->revmap_tree_mutex);
                radix_tree_insert(&domain->revmap_tree, hwirq, irq_data);   // 向radix tree插入一个node
                mutex_unlock(&domain->revmap_tree_mutex);
        }
}
复制代码

第一个参数为中断域,第二个参为硬件中断号,第三个参数为该硬件中断对应的irq_data。

值得一提的是,linux内核提供了为中断域某个硬件中断号动态分配IRQ编号的函数irq_create_mapping,这里之所以没有使用的主要原因是由于在旧的linux kernel中,各个外设的IRQ是固定的(比如S3C2440,有大量针对IRQ编号的宏定义),也就是说,硬件中断号和IRQ编号的关系是固定的,为了兼容低版本,因此不能使用该方法。

unsigned int irq_create_mapping(struct irq_domain *domain, irq_hw_number_t hwirq);

irq_create_mapping函数输入参数是中断域和硬件中断号,返回IRQ编号。该函数:

  • 首先判断hwirq映射是否存在,如果存在,直接返回;
  • 调用irq_domain_alloc_descs为硬件中断动态申请中断描述符,将其与hwirq关联,同时为硬件中断号申请一个全局IRQ编号,并返回IRQ编号;
  • 然后调用irq_domain_associate把硬件中断号到IRQ编号的映射添加到中断域;同时调用domain->ops->map回调,以便驱动程序可以执行任何所需的硬件设置;

函数调用流程:

irq_create_mapping(domain, hwirq) 
     virq  = irq_find_mapping(domain, hwirq)    // 判断hwirq映射是否存在,如果存在,直接返回
     irq_domain_alloc_descs(-1, 1, hwirq, of_node_to_nid(of_node), NULL) // 为硬件中断动态申请中断描述符
     irq_domain_associate(domain, virq, hwirq)   // 为中断域添加hwirq到virq的映射
         domain->ops->map(domain, virq, hwirq)   // 回调domain操作集map函数  
         irq_domain_set_mapping(domain, hwirq, irq_data); // 把硬件中断号到IRQ编号的映射添加到中断域的线性表linear_revmap  
4.2.3 查找映射

中断处理程序需要根据硬件中断号查找IRQ编号,内核提供了函数irq_find_mapping,输入参数是中断域和硬件中断号,返回IRQ编号。函数定义在kernel/irq/irqdomain.c:

复制代码
/**
 * irq_find_mapping() - Find a linux irq from a hw irq number.
 * @domain: domain owning this hardware interrupt
 * @hwirq: hardware irq number in that domain space
 */
unsigned int irq_find_mapping(struct irq_domain *domain,
                              irq_hw_number_t hwirq)
{
        struct irq_data *data;

        /* Look for default domain if nececssary */
        if (domain == NULL)
                domain = irq_default_domain;
        if (domain == NULL)
                return 0;

        if (hwirq < domain->revmap_direct_max_irq) {
                data = irq_domain_get_irq_data(domain, hwirq);  
                if (data && data->hwirq == hwirq)
                        return hwirq;
        }

        /* Check if the hwirq is in the linear revmap. */
        if (hwirq < domain->revmap_size)
                return domain->linear_revmap[hwirq];  // 线性映射表 数组索引为硬件中断号

        rcu_read_lock();
        data = radix_tree_lookup(&domain->revmap_tree, hwirq);
        rcu_read_unlock();
        return data ? data->irq : 0;
}
复制代码

收到中断时,应使用irq_find_mapping函数根据hwirq号查找IRQ编号。当然,必须在返回之前存在该映射,IRQ编号始终与irq_desc数据结构绑定。

4.3 设置中断流控处理函数

domain->ops指向了s3c24xx_irq_ops,其成员map指向了s3c24xx_irq_map,也是定义在drivers/irqchip/irq-s3c24xx.c:

复制代码
static int s3c24xx_irq_map(struct irq_domain *h, unsigned int virq,    
                                                        irq_hw_number_t hw)  
{
        struct s3c_irq_intc *intc = h->host_data;         // 获取中断域绑定的中断控制器
        struct s3c_irq_data *irq_data = &intc->irqs[hw];  // 根据硬件中断号获取中断源信息
        struct s3c_irq_intc *parent_intc;
        struct s3c_irq_data *parent_irq_data;
        unsigned int irqno;

        /* attach controller pointer to irq_data */
        irq_data->intc = intc;
        irq_data->offset = hw;                   // 硬件中断在中断相关寄存器的偏移位

        parent_intc = intc->parent;              // 如果有父中断控制器

        /* set handler and flags */
        switch (irq_data->type) {    // 根据中断源类型设置中断处理函数
        case S3C_IRQTYPE_NONE:
                return 0;
        case S3C_IRQTYPE_EINT:                // 外部中断
                /* On the S3C2412, the EINT0to3 have a parent irq
                 * but need the s3c_irq_eint0t4 chip
                 */
                if (parent_intc && (!soc_is_s3c2412() || hw >= 4))             // 对于子中断控制器,走这里
                        irq_set_chip_and_handler(virq, &s3c_irqext_chip,
                                                 handle_edge_irq);
                else                                          
                        irq_set_chip_and_handler(virq, &s3c_irq_eint0t4,
                                                 handle_edge_irq);
                break;
        case S3C_IRQTYPE_EDGE:            // 双边沿触发中断 
                if (parent_intc || intc->reg_pending == S3C2416_SRCPND2)   // 对于子中断控制器,走这里
                        irq_set_chip_and_handler(virq, &s3c_irq_level_chip,
                                                 handle_edge_irq);
                else
                        irq_set_chip_and_handler(virq, &s3c_irq_chip,
                                                 handle_edge_irq);
                break;
        case S3C_IRQTYPE_LEVEL:            // 电平触发  
                if (parent_intc)                    // 对于子中断控制器走这里
                        irq_set_chip_and_handler(virq, &s3c_irq_level_chip,
                                                 handle_level_irq);
                else
                        irq_set_chip_and_handler(virq, &s3c_irq_chip,
                                                 handle_level_irq);
                break;
        default:
                pr_err("irq-s3c24xx: unsupported irqtype %d\n", irq_data->type);
                return -EINVAL;
        }
        irq_set_chip_data(virq, irq_data);

        if (parent_intc && irq_data->type != S3C_IRQTYPE_NONE) {  // 有父级中断控制器,即子中断控制器,需要修改主中断源的中断处理函数为s3c_irq_demux
                if (irq_data->parent_irq > 31) {
                        pr_err("irq-s3c24xx: parent irq %lu is out of range\n",
                               irq_data->parent_irq);
                        return -EINVAL;
                }

                parent_irq_data = &parent_intc->irqs[irq_data->parent_irq];  // 获取子中断对应的主中断源数据
                parent_irq_data->sub_intc = intc;                  // 设置子中断中断控制器
                parent_irq_data->sub_bits |= (1UL << hw);

                /* attach the demuxer to the parent irq */
                irqno = irq_find_mapping(parent_intc->domain,   // 根据主中断硬件中断号和中断域获取主中断编号
                                         irq_data->parent_irq);
                if (!irqno) {
                        pr_err("irq-s3c24xx: could not find mapping for parent irq %lu\n",
                               irq_data->parent_irq);
                        return -EINVAL;
                }
                irq_set_chained_handler(irqno, s3c_irq_demux);   // 设置子中断的主中断源的中断处理函数
        }

        return 0;
}
复制代码

第一个参数为中断域、第二个参数为IRQ编号,第三个为硬件中断号。

这里做了以下事情:

(1) 通过中断域irq_domain获取到其关联的中断控制器s3c_irq_intc;

(2) 根据硬件中断号hw从中断控制器的irqs获取中断源信息;

(3) 然后调用irq_set_chip_and_handler,根据IRQ编号(虚拟中断号)找到irq_desc,然后根据不同的中断触发类型,设置其成员handle_irq回调和irq_chip指针;

(4) 如果是子中断,还需要调用irq_set_chained_handler设置主中断所对应的irq_desc->handle_irq回调函数;

  • 主要是因为子中断控制器在把多个irq汇集起来后,输出端连接到根中断控制器的其中一个irq中断线输入脚,这意味着,每个子中断控制器的中断发生时,CPU一开始只会得到根中断控制器的irq编号,然后进入该irq编号对应的irq_desc->handle_irq回调,该回调我们不能使用流控层定义好的几个流控函数,而是要自己实现一个函数,比如这里的s3c_irq_demux函数:
  • s3c_irq_demux函数负责从子中断控制器中获得irq的中断源,并计算出对应新的irq编号,然后调用新irq所对应的irq_desc->handle_irq回调,这个回调使用流控层的标准实现;
4.3.1 s3c_irq_demux

s3c_irq_demux为S3C2440主中断源对应的中断处理函数,定义在drivers/irqchip/irq-s3c24xx.c,由于一个主中断源一般对应多个子中断;

因此这里首先会获取到子中断控制器,然后读取中断挂起寄存器,根据寄存器请求位,找到子中断IRQ编号,然后调用generic_handle_irq。

复制代码
static void s3c_irq_demux(struct irq_desc *desc)
{
        // 根据中断描述符获取中断控制器irq_chip 这个是主中断源对应的中断控制器
        struct irq_chip *chip = irq_desc_get_chip(desc);     // desc->irq_data.chip
        struct s3c_irq_data *irq_data = irq_desc_get_chip_data(desc);
        // 获取中断控制器s3c_irq_intc
        struct s3c_irq_intc *intc = irq_data->intc;
        // 获取子中断控制器
        struct s3c_irq_intc *sub_intc = irq_data->sub_intc;
        unsigned int n, offset, irq;
        unsigned long src, msk;

        /* we're using individual domains for the non-dt case
         * and one big domain for the dt case where the subintc
         * starts at hwirq number 32.
         */
        offset = irq_domain_get_of_node(intc->domain) ? 32 : 0;

        chained_irq_enter(chip, desc);
 
        src = readl_relaxed(sub_intc->reg_pending);   // 读取中断挂起寄存器
        msk = readl_relaxed(sub_intc->reg_mask);      // 读取中断屏蔽寄存器

        src &= ~msk;
        src &= irq_data->sub_bits;

        while (src) {
                n = __ffs(src);
                src &= ~(1 << n);
                // 获取子中断控制器sub_intc硬件中断offset + n对应的IRQ编号
                irq = irq_find_mapping(sub_intc->domain, offset + n);
                // 执行子中断中断处理函数
                generic_handle_irq(irq);
        }

        chained_irq_exit(chip, desc);
}
复制代码
4.3.2 struct irq_chip

s3c24xx_irq_map函数根据中断触发类型,为每个中断描述符绑定不同的struct irq_chip;比如:

  • 对于中断触发类型为S3C_IRQTYPE_EINT的子中断,其对应的irq_desc->irq_data.chip设置为s3c_irqext_chip;覆盖的中断范围为外部中断EINT4~EINT23;
  • 对于中断触发类型为S3C_IRQTYPE_EINT的主中断,其对应的irq_desc->irq_data.chip设置为s3c_irq_eint0t4;覆盖的中断范围为外部中断EINT0~EINT3;
  • 对于中断触发类型为S3C_IRQTYPE_LEVEL的子中断,其对应的irq_desc->irq_data.chip设置为s3c_irq_level_chip;覆盖的中断范围为带有子中断的内部中断;
  • 对于中断触发类型为S3C_IRQTYPE_LEVEL的主中断,其对应的irq_desc->irq_data.chip设置为s3c_irq_chip;覆盖的中断范围为没有子中断的内部中断;
  • 对于中断触发类型为S3C_IRQTYPE_EDGE的子中断,其对应的irq_desc->irq_data.chip设置为s3c_irq_level_chip;覆盖的中断范围为带有子中断的内部中断;
  • 对于中断触发类型为S3C_IRQTYPE_EDGE的主中断,其对应的irq_desc->irq_data.chip设置为s3c_irq_chip;覆盖的中断范围为没有子中断的内部中断;

s3c_irqext_chip、s3c_irq_eint0t4、s3c_irq_level_chip、s3c_irq_chip均是struct irq_chip类型:

复制代码
static struct irq_chip s3c_irq_chip = {
        .name           = "s3c",
        .irq_ack        = s3c_irq_ack,
        .irq_mask       = s3c_irq_mask,
        .irq_unmask     = s3c_irq_unmask,
        .irq_set_type   = s3c_irq_type,
        .irq_set_wake   = s3c_irq_wake
};

static struct irq_chip s3c_irq_level_chip = {
        .name           = "s3c-level",
        .irq_mask       = s3c_irq_mask,
        .irq_unmask     = s3c_irq_unmask,
        .irq_ack        = s3c_irq_ack,
        .irq_set_type   = s3c_irq_type,
};

static struct irq_chip s3c_irqext_chip = {
        .name           = "s3c-ext",
        .irq_mask       = s3c_irq_mask,
        .irq_unmask     = s3c_irq_unmask,
        .irq_ack        = s3c_irq_ack,
        .irq_set_type   = s3c_irqext_type,
        .irq_set_wake   = s3c_irqext_wake
};

static struct irq_chip s3c_irq_eint0t4 = {
        .name           = "s3c-ext0",
        .irq_ack        = s3c_irq_ack,
        .irq_mask       = s3c_irq_mask,
        .irq_unmask     = s3c_irq_unmask,
        .irq_set_wake   = s3c_irq_wake,
        .irq_set_type   = s3c_irqext0_type,
};
复制代码

struct irq_chip对中断控制器的接口抽象;其中的成员大多用于操作底层硬件,比如设置寄存器以屏蔽中断,使能中断,清除中断等。

这里以s3c_irq_mask为例,用于屏蔽与s3c_irq_chip中断控制器关联的中断;

  • 对于主中断:其本质就是根据硬件中断号设置主中断控制器中断屏蔽寄存器对应位为1;
  • 对于子中断:其本质就是根据硬件中断号设置子中断控制器中断屏蔽寄存器对应位为1;

代码如下:

复制代码
static void s3c_irq_mask(struct irq_data *data)  // data为当前中断的数据
{
        struct s3c_irq_data *irq_data = irq_data_get_irq_chip_data(data);
        struct s3c_irq_intc *intc = irq_data->intc;
        struct s3c_irq_intc *parent_intc = intc->parent;  // 父中断控制器
        struct s3c_irq_data *parent_data;
        unsigned long mask;
        unsigned int irqno;

        mask = readl_relaxed(intc->reg_mask);
        mask |= (1UL << irq_data->offset);
        writel_relaxed(mask, intc->reg_mask);

        if (parent_intc) {   // 如果是子中断,判断该子中断所属的主中断是不是还有其它子中断在使用,如果没有的话,也会把该主中断也给屏蔽了
                parent_data = &parent_intc->irqs[irq_data->parent_irq];

                /* check to see if we need to mask the parent IRQ
                 * The parent_irq is always in main_intc, so the hwirq
                 * for find_mapping does not need an offset in any case.
                 */
                if ((mask & parent_data->sub_bits) == parent_data->sub_bits) {
                        irqno = irq_find_mapping(parent_intc->domain,
                                         irq_data->parent_irq);
                        s3c_irq_mask(irq_get_irq_data(irqno));
                }
        }
}
复制代码
4.3.3 irq_set_chip_and_handler

irq_set_chip_and_handler定义在include/linux/irq.h文件中,用于根据IRQ编号设置中断流控处理函数、以及中断控制器(struct irq_chip);

static inline void irq_set_chip_and_handler(unsigned int irq, struct irq_chip *chip,
                                            irq_flow_handler_t handle)
{
        irq_set_chip_and_handler_name(irq, chip, handle, NULL);
}

该函数接收IRQ编号、中断控制器(struct irq_chip)以及中断流控处理函数。

irq_set_chip_and_handler_name定义在 kernel/irq/chip.c:

void irq_set_chip_and_handler_name(unsigned int irq, struct irq_chip *chip,
                              irq_flow_handler_t handle, const char *name)
{
        irq_set_chip(irq, chip);
        __irq_set_handler(irq, handle, 0, name);
}

首先调用irq_set_chip绑定irq到chip;然后调用__irq_set_handler初始化中断描述符的handle_irq回调函数:

复制代码
void
__irq_set_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained,
                  const char *name)
{
        unsigned long flags;
        struct irq_desc *desc = irq_get_desc_buslock(irq, &flags, 0);

        if (!desc)
                return;

        __irq_do_set_handler(desc, handle, is_chained, name);
        irq_put_desc_busunlock(desc, flags);
}
复制代码

4.4  设置中断统一入口函数handle_arch_irq

s3c24xx_clear_intc函数最后调用set_handle_irq(s3c24xx_handle_irq)设置中断统一入口函数为s3c24xx_handle_irq,位于kernel/irq/handle.c文件

复制代码
#ifdef CONFIG_GENERIC_IRQ_MULTI_HANDLER
int __init set_handle_irq(void (*handle_irq)(struct pt_regs *))
{
        if (handle_arch_irq)
                return -EBUSY;

        handle_arch_irq = handle_irq;
        return 0;
}
复制代码

可以看到如果我们定义了CONFIG_GENERIC_IRQ_MULTI_HANDLER,将会设置handle_arch_irq为s3c24xx_handle_irq,有没有发现这里很熟悉,这是因为在之前我们介绍irq_hanlder中就是调用的handle_arch_irq函数进行中断处理的。

复制代码
.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
9997:
        .endm
复制代码

之前我们分析的是中断处理的默认实现:arch_irq_handler_default。

既然看到这里了,那我顺便也看一下s3c24xx_handle_irq的实现,位于drivers/irqchip/irq-s3c24xx.c:

复制代码
asmlinkage void __exception_irq_entry s3c24xx_handle_irq(struct pt_regs *regs)
{
        do {
                if (likely(s3c_intc[0]))
                        if (s3c24xx_handle_intc(s3c_intc[0], regs, 0))   // 执行这里
                                continue;

                if (s3c_intc[2])
                        if (s3c24xx_handle_intc(s3c_intc[2], regs, 64))
                                continue;

                break;
        } while (1);
}
复制代码

传入的struct pt_regs *regs参数其实就是之前保存现场的那些寄存器:

复制代码
static inline int s3c24xx_handle_intc(struct s3c_irq_intc *intc,              
                                      struct pt_regs *regs, int intc_offset)   // 硬件中断号起始偏移
{
        int pnd;
        int offset;

        pnd = readl_relaxed(intc->reg_intpnd);  // 读取中断挂起寄存器,从而知道触发了哪些中断源
        if (!pnd)
                return false;

        /* non-dt machines use individual domains */
        if (!irq_domain_get_of_node(intc->domain))
                intc_offset = 0;

        /* We have a problem that the INTOFFSET register does not always
         * show one interrupt. Occasionally we get two interrupts through
         * the prioritiser, and this causes the INTOFFSET register to show
         * what looks like the logical-or of the two interrupt numbers.
         *
         * Thanks to Klaus, Shannon, et al for helping to debug this problem
         */
        offset = readl_relaxed(intc->reg_intpnd + 4);    // 读取中断偏移寄存器 获取硬件中断号

        /* Find the bit manually, when the offset is wrong.
         * The pending register only ever contains the one bit of the next
         * interrupt to handle.
         */
        if (!(pnd & (1 << offset)))                    // 再次校验发生了中断
                offset =  __ffs(pnd);

        handle_domain_irq(intc->domain, intc_offset + offset, regs);
        return true;
}
复制代码

s3c24xx_handle_intc先读取中断挂起寄存器,判断有没有中断发生,然后读取中断偏移寄存器,根据寄存器请求位,找到的中断号,然后调用handle_domain_irq。

handle_domain_irq函数定义在include/linux/irqdesc.h文件:

亲爱的读者和支持者们,自动博客加入了打赏功能,陆陆续续收到了各位老铁的打赏。在此,我想由衷地感谢每一位对我们博客的支持和打赏。你们的慷慨与支持,是我们前行的动力与源泉。

日期姓名金额
2023-09-06*源19
2023-09-11*朝科88
2023-09-21*号5
2023-09-16*真60
2023-10-26*通9.9
2023-11-04*慎0.66
2023-11-24*恩0.01
2023-12-30I*B1
2024-01-28*兴20
2024-02-01QYing20
2024-02-11*督6
2024-02-18一*x1
2024-02-20c*l18.88
2024-01-01*I5
2024-04-08*程150
2024-04-18*超20
2024-04-26.*V30
2024-05-08D*W5
2024-05-29*辉20
2024-05-30*雄10
2024-06-08*:10
2024-06-23小狮子666
2024-06-28*s6.66
2024-06-29*炼1
2024-06-30*!1
2024-07-08*方20
2024-07-18A*16.66
2024-07-31*北12
2024-08-13*基1
2024-08-23n*s2
2024-09-02*源50
2024-09-04*J2
2024-09-06*强8.8
2024-09-09*波1
2024-09-10*口1
2024-09-10*波1
2024-09-12*波10
2024-09-18*明1.68
2024-09-26B*h10
2024-09-3010
2024-10-02M*i1
2024-10-14*朋10
2024-10-22*海10
2024-10-23*南10
2024-10-26*节6.66
2024-10-27*o5
2024-10-28W*F6.66
2024-10-29R*n6.66
2024-11-02*球6
2024-11-021*鑫6.66
2024-11-25*沙5
2024-11-29C*n2.88
posted @   大奥特曼打小怪兽  阅读(690)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
如果有任何技术小问题,欢迎大家交流沟通,共同进步

公告 & 打赏

>>

欢迎打赏支持我 ^_^

最新公告

程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)。

了解更多

点击右上角即可分享
微信分享提示