zoukankan      html  css  js  c++  java
  • Linux中断体系结构

    1.中断处理体系结构

    Linux内核将所有中断统一编号,使用一个irq_desc结构数组来描述这些中断。

    数组声明在/linux/kernel/irq/handle.c中,其中#define NR_IRQS 128,定义在/linux/include/asm/irq.h

     1 /*
     2  * Linux has a controller-independent interrupt architecture.
     3  * Every controller has a 'controller-template', that is used
     4  * by the main code to do the right thing. Each driver-visible
     5  * interrupt source is transparently wired to the appropriate
     6  * controller. Thus drivers need not be aware of the
     7  * interrupt-controller.
     8  *
     9  * The code is designed to be easily extended with new/different
    10  * interrupt controllers, without having to do assembly magic or
    11  * having to touch the generic code.
    12  *
    13  * Controller mappings for all interrupt sources:
    14  */
    15 struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
    16     [0 ... NR_IRQS-1] = {
    17         .status = IRQ_DISABLED,
    18         .chip = &no_irq_chip,
    19         .handle_irq = handle_bad_irq,
    20         .depth = 1,
    21         .lock = __SPIN_LOCK_UNLOCKED(irq_desc->lock),
    22 #ifdef CONFIG_SMP
    23         .affinity = CPU_MASK_ALL
    24 #endif
    25     }
    26 };
    irq_desc结构的数据类型在/linuxinclude/linux/irq.h中定义,
     1 struct irq_desc {
     2     irq_flow_handler_t    handle_irq;
     3     struct irq_chip        *chip;
     4     struct msi_desc        *msi_desc;
     5     void            *handler_data;
     6     void            *chip_data;
     7     struct irqaction    *action;    /* IRQ action list */
     8     unsigned int        status;        /* IRQ status */
     9 
    10     unsigned int        depth;        /* nested irq disables */
    11     unsigned int        wake_depth;    /* nested wake enables */
    12     unsigned int        irq_count;    /* For detecting broken IRQs */
    13     unsigned int        irqs_unhandled;
    14     spinlock_t        lock;
    15 #ifdef CONFIG_SMP
    16     cpumask_t        affinity;
    17     unsigned int        cpu;
    18 #endif
    19 #if defined(CONFIG_GENERIC_PENDING_IRQ) || defined(CONFIG_IRQBALANCE)
    20     cpumask_t        pending_mask;
    21 #endif
    22 #ifdef CONFIG_PROC_FS
    23     struct proc_dir_entry    *dir;
    24 #endif
    25     const char        *name;
    26 } ____cacheline_internodealigned_in_smp;

    handle_irq是这个或这组中断的处理函数入口。发生中断时,总入口函数asm_do_IRQ将根据中断号调用相应irq_desc数组项中handle_irq.

    typedef    void fastcall (*irq_flow_handler_t)(unsigned int irq,                struct irq_desc *desc);

    handle_irq使用chip结构中的函数清除、屏蔽或者重新使能中断,还要调用用户在action链表中注册的中断处理函数。irq_chip结构类型也是在include/linux/irq.h中定义,其中的成员大多用于操作底层硬件,比如设置寄存器以屏蔽中断,使能中断,清除中断等。注意这里的成员name会出现在/proc/interrupts中。

    struct irq_chip {
        const char    *name;
        unsigned int    (*startup)(unsigned int irq);
        void        (*shutdown)(unsigned int irq);
        void        (*enable)(unsigned int irq);
        void        (*disable)(unsigned int irq);
    
        void        (*ack)(unsigned int irq);
        void        (*mask)(unsigned int irq);
        void        (*mask_ack)(unsigned int irq);
        void        (*unmask)(unsigned int irq);
        void        (*eoi)(unsigned int irq);
    
        void        (*end)(unsigned int irq);
        void        (*set_affinity)(unsigned int irq, cpumask_t dest);
        int        (*retrigger)(unsigned int irq);
        int        (*set_type)(unsigned int irq, unsigned int flow_type);
        int        (*set_wake)(unsigned int irq, unsigned int on);
    
        /* Currently used only by UML, might disappear one day.*/
    #ifdef CONFIG_IRQ_RELEASE_METHOD
        void        (*release)(unsigned int irq, void *dev_id);
    #endif
        /*
         * For compatibility, ->typename is copied into ->name.
         * Will disappear.
         */
        const char    *typename;
    };

    irq_desc结构中的irqaction结构类型在include/linux/iterrupt.h中定义。用户注册的每个中断处理函数用一个irqaction结构来表示,一个中断比如共享中断可以有多个处理函数,它们的irqaction结构链接成一个链表,以action为表头。irqation结构在linux/include/linux/interrupt.h中定义如下:

    typedef irqreturn_t (*irq_handler_t)(int, void *);
    
    struct irqaction {
        irq_handler_t handler;
        unsigned long flags;
        cpumask_t mask;
        const char *name;
        void *dev_id;
        struct irqaction *next;
        int irq;
        struct proc_dir_entry *dir;
    };

     irq_desc结构数组、它的成员“struct irq_chip *chip” "struct irqaction *action",这3种数据结构构成了中断处理体系的框架。下图中描述了Linxu中断处理体系结构的关系图:

    中断处理流程如下
    (1)发生中断时,CPU执行异常向量vector_irq的代码
    (2)在vector_irq里面,最终会调用中断处理的总入口函数asm_do_IRQ
    (3)asm_do_IRQ根据中断号调用irq_desc数组项中的handle_irq。
    (4)handle_irq会使用chip成员中的函数来设置硬件,比如清除中断、禁止中断、重新使能中断等
    (5)handle_irq逐个调用用户在aciton链表中注册的处理函数
       中断体系结构的初始化就是构造这些数据结构,比如irq_desc数组项中的handle_irq、chip等成员;用户注册中断时就是构造action链表;用户卸载中断时就是从action链表中去除不需要的项。
     
    2.中断处理体系结构的初始化
    init_IRQ函数被用来初始化中断处理体系结构,代码在arch/arm/kernel/irq.c中
    void __init init_IRQ(void)
    {
        int irq;
    
        for (irq = 0; irq < NR_IRQS; irq++)
            irq_desc[irq].status |= IRQ_NOREQUEST | IRQ_NOPROBE;
    
    #ifdef CONFIG_SMP
        bad_irq_desc.affinity = CPU_MASK_ALL;
        bad_irq_desc.cpu = smp_processor_id();
    #endif
        init_arch_irq();
    }

    下面简单分析下init_arch_irq();的获取过程及调用顺序

    1 /*
    2 init_arch_irq()的由来
    3 定义一个空函数指针void (*init_arch_irq)(void) __initdata = NULL;
    4 */
    5 asmlinkage void __init start_kernel(void)
    6     -->setup_arch(&command_line);
    7         -->init_arch_irq = mdesc->init_irq;
    8     -->init_IRQ();    
    9         -->init_arch_irq()//即mdesc->init_irq=s3c24xx_init_irq

    上述machine_desc结构在/linux/arch/arm/mach-s3c2410/mach-bast.c如下宏中获得,后面会分析machine_desc结构。

    MACHINE_START(BAST, "Simtec-BAST")
        //Maintainer: Ben Dooks <ben@simtec.co.uk> 
        .phys_io    = S3C2410_PA_UART,
        .io_pg_offst    = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
        .boot_params    = S3C2410_SDRAM_PA + 0x100,
        .map_io        = bast_map_io,
        .init_irq    = s3c24xx_init_irq,
        .init_machine    = bast_init,
        .timer        = &s3c24xx_timer,
    MACHINE_END

    对于S3C2440开发板,这个函数就是s3c24xx_init_irq,移植machine_desc结构中的init_irq成员就指向这个函数s3c24xx_init_irq函数在arch/arm/plat-s3c24xx/irq.c中定义,它为所有中断设置了芯片相关的数据结构(irq_desc[irq].chip),设置了处理函数入口(irq_desc[irq].handle_irq)。

     以外部中断EINT4-EINT23为例,用来设置它们的代码如下:
    void __init s3c24xx_init_irq(void)
    {
        ...
        
        for (irqno = IRQ_EINT4; irqno <= IRQ_EINT23; irqno++) {
            irqdbf("registering irq %d (extended s3c irq)
    ", irqno);
            set_irq_chip(irqno, &s3c_irqext_chip);
            set_irq_handler(irqno, handle_edge_irq);
            set_irq_flags(irqno, IRQF_VALID);
        }
        ...    
        
    }

    set_irq_chip函数的作用就是“irq_desc[irno].chip = &s3c_irqext_chip”,以后就可能通过irq_desc[irqno].chip结构中的函数指针设置这些外部中断的触发方式(电平触发,边沿触发),使能中断,禁止中断。

    set_irq_chip函数的作用就是“irq_desc[irno].handler_irq = handle_edge_irq”,设置这些中断的处理函数入口为handle_edge_irq。发生中断时handle_edge_irq函数会调用用户注册的具体处理函数;

    set_irq_flags设置中断标志为“IRQF_VALID”,表示可以使用它们。

    init_IRQ函数执行完后,irq_desc数组项的chip,handl_irq成员都被设置。

    3.用户注册中断过程分析

    调用/linux/kernel/irq/manage.c中的request_irq函数。

    int request_irq(unsigned int irq, irq_handler_t handler, unsigned long irqflags, const char *devname, void *dev_id)
        -->struct irqaction *action = kmalloc(sizeof(struct irqaction), GFP_ATOMIC);
        -->action->handler = handler;
        -->action->flags = irqflags;
        -->cpus_clear(action->mask);
        -->action->name = devname;
        -->action->next = NULL;
        -->action->dev_id = dev_id;
        -->retval = setup_irq(irq, action);
            -->request_irq函数根据中断号找到irq_desc数组项,然后在它的action链表添加一个表
            -->irq_chip_set_defaults(desc->chip);//chip成员在init_IRQ()中已经设置,这里设置其中还没有设置的。
            -->设置中断触发方式
            -->启动中断

    3.1将新建的irqaction结构链入irq_desc[irq]结构的action链表中

    这有两种可能。如果action链表为空,则直接链入;否则先判断新建的irqaction结构和链表中的irqaction结构所表示的中断类型是否一致,即是否都声明为"可共享的"(IRQF_SHARED)、是否都使用相同的触发方式,如果一致,则将新建的irqation结构链入。
    3.2设置irq_desc[irq]结构中chip成员的还没设置的指针

    让它们指向一些默认函数。chip成员在init_IRQ函数初始化中断体系结构的时候已经设置了,这里只是设置其中还没设置的指针这通过irq_chip_set_defaults函数来完成,它在kernel/irq/chip.c中定义

    3.3设置中断的触发方式

    如果requestt_irq传入的irqflags参数表示中断的触发方式为高低电平触发/边沿触发,则调用irq_desc[irq]结构中的chip-.set_type成员函数来进行设置:设置引脚功能为外部中断,设置中断触发方式。

    3.4启动中断

    如果irq_desc[irq]结构中status成员没有被指明IRQ_NOAUTOEN(表示注册中断时不要使用中断),还要调用chip->startup或chip->enable来启动中断,所谓启动中断通常就是使用中断。一般情况下,只有那些“可以自动使能的”中断对应的irq_desc[irq].status才会被指明为IRQ_NOAUTOEN,所以,无论哪种情况,执行request_irq注册中断之后,这个中断就已经被使能了。

    //-----------------------------------------------

    /*
    NR_IRQS定义in linux/arch/arm/plat-s3c64xx/include/mach/irqs.h
    */
    #define NR_IRQS (IRQ_EINT_GROUP9_BASE + IRQ_EINT_GROUP9_NR + 1)


    asmlinkage void __init start_kernel(void)
    -->early_irq_init();
    -->init_IRQ();
    -->init_arch_irq();

    /*
    arch/arm/kernel/irq.c中声明init_arch_irq函数指针
    */
    void (*init_arch_irq)(void) __initdata = NULL; /*全局函数指针*/
    void __init setup_arch(char **cmdline_p)
    -->init_arch_irq = mdesc->init_irq;//s3c6410_init_irq

    /*
    linux/arch/arm/mach-s3c6410/mach-smdk6410.c中定义machine_desc结构体
    */
    MACHINE_START(SMDK6410, "SMDK6410")
    /* Maintainer: Ben Dooks <ben@fluff.org> */
    .phys_io = S3C_PA_UART & 0xfff00000,
    .io_pg_offst = (((u32)S3C_VA_UART) >> 18) & 0xfffc,
    .boot_params = S3C64XX_PA_SDRAM + 0x100,

    .init_irq = s3c6410_init_irq,
    .map_io = smdk6410_map_io,
    .init_machine = smdk6410_machine_init,
    .timer = &s3c24xx_timer,
    MACHINE_END

    /*
    s3c6410_init_irq in linux/arch/arm/mach-s3c6410/cpu.c
    */
    void __init s3c64xx_init_irq(u32 vic0_valid, u32 vic1_valid)
    {
    int uart, irq;

    printk(KERN_DEBUG "%s: initialising interrupts ", __func__);

    /* initialise the pair of VICs */
    vic_init(S3C_VA_VIC0, S3C_VIC0_BASE, vic0_valid);
    vic_init(S3C_VA_VIC1, S3C_VIC1_BASE, vic1_valid);

    /* add the timer sub-irqs */

    set_irq_chained_handler(IRQ_TIMER0_VIC, s3c_irq_demux_timer0);
    set_irq_chained_handler(IRQ_TIMER1_VIC, s3c_irq_demux_timer1);
    set_irq_chained_handler(IRQ_TIMER2_VIC, s3c_irq_demux_timer2);
    set_irq_chained_handler(IRQ_TIMER3_VIC, s3c_irq_demux_timer3);
    set_irq_chained_handler(IRQ_TIMER4_VIC, s3c_irq_demux_timer4);

    for (irq = IRQ_TIMER0; irq <= IRQ_TIMER4; irq++) {
    set_irq_chip(irq, &s3c_irq_timer);
    set_irq_handler(irq, handle_level_irq);
    set_irq_flags(irq, IRQF_VALID);
    }

    for (uart = 0; uart < ARRAY_SIZE(uart_irqs); uart++)
    s3c64xx_uart_irq(&uart_irqs[uart]);
    }

    #define IRQ_EINT_GROUP(group, no) (IRQ_EINT_GROUP##group##_BASE + (no))
    /*
    IRQ_EINT_GROUP(1, 3)展开为
    IRQ_EINT_GROUP1_BASE + 3
    */

  • 相关阅读:
    Linux 中改变主机名的 4 种方法
    如何成为优秀开发人员(一):怎样算是优秀的?
    Java中需要知道的关键字
    Java集合类常见的问题
    如何在 Linux 上复制文件/文件夹到远程系统?
    你还在 Select * 吗?
    技术人解决问题的思路
    如何创建编程语言,以及设计决策中的内容?
    Java内存溢出异常(下)
    如何在 Linux 中查看可用的网络接口
  • 原文地址:https://www.cnblogs.com/yangjiguang/p/7631539.html
Copyright © 2011-2022 走看看