• 内核中断机制


    内核版本:linux2.6.22.6   硬件平台:JZ2440

    a. 异常向量入口: archarmkernelentry-armv.S
    
        .section .vectors, "ax", %progbits
    .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
    
    b. 中断向量: vector_irq
    /*
     * Interrupt dispatcher
     */
        vector_stub irq, IRQ_MODE, 4   // 相当于 vector_irq: ..., 
                                       // 它会根据SPSR寄存器的值,
                                       // 判断被中断时CPU是处于USR状态还是SVC状态, 
                                       // 然后调用下面的__irq_usr或__irq_svc
    
        .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
    
    c. __irq_usr/__irq_svc
    
        __irq_usr:
        usr_entry                  // 保存现场
        kuser_cmpxchg_check
        irq_handler                  // 调用 irq_handler
        get_thread_info tsk
        mov    why, #0
        b    ret_to_user_from_irq   // 恢复现场
    
       
    d. irq_handler: 将会调用C函数 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
        
    e. handle.c
       set_handle_irq( pointer_to_fun() )
            handle_arch_irq = pointer_to_fun()
            
    f. handle_arch_irq的处理过程: (读取寄存器而分辨哪个中断,并调用对应的中断处理函数)
       读取寄存器获得中断信息: hwirq
       把hwirq转换为virq                    // hwirq为硬件中断号,virq为虚拟中断号 (两者间有偏移值)
                                            // #define S3C2410_CPUIRQ_OFFSET (16)   #define S3C2410_IRQ(x) ((x) + S3C2410_CPUIRQ_OFFSET)
       调用 1.irq_desc[virq].handle_irq              //处理中断
            2.irq_desc[virq].irq_data.irq_chip.fun()     // 清中断
       
        对于S3C2440, s3c24xx_handle_irq 是用于处理中断的C语言入口函数
        set_handle_irq(s3c24xx_handle_irq);
        
     注:
        irq_desc[nr_irqs]                  // 包含有多个irq_desc结构体,每个对应不同的中断
        
        struct irq_desc 
        {
        struct irq_data        irq_data;     // 带有具体处理中断函数
        irq_flow_handler_t    handle_irq;   // 1.调用action链表中的handler(也就是具体的处理函数) 2.再清中断(使用irq_data->chip的函数)
        struct irqaction    *action;      // 指向irqaction链表
        }
    
        struct irqaction 
        {
        irq_handler_t        handler;      // 中断的具体处理函数
        void                *dev_id;    
        struct irqaction    *next;
        } 
    
        struct irq_data 
        {
        u32            mask;
        unsigned int        irq;
        unsigned long        hwirq;
        struct irq_common_data    *common;
        struct irq_chip        *chip;        // 很多中断操作函数
        struct irq_domain    *domain;
        void            *chip_data;
        };
        
        
        struct irq_chip 
        {
        void    (*irq_enable)(struct irq_data *data);   // 使能中断函数
        void    (*irq_disable)(struct irq_data *data);  // 去使能中断函数
        void    (*irq_ack)(struct irq_data *data);
        void    (*irq_mask)(struct irq_data *data);        //屏蔽中断函数
        void    (*irq_unmask)(struct irq_data *data);
        }
        
        
        
        
        
    中断处理流程:
    假设中断结构如下:
    sub int controller ---> int controller ---> cpu
    
    发生中断时,
    cpu跳到"vector_irq", 保存现场, 调用C函数handle_arch_irq
    
    handle_arch_irq:
    a. 读 int controller, 得到hwirq
    b. 根据hwirq得到virq
    c. 调用 irq_desc[virq].handle_irq
    
    如果该中断没有子中断, irq_desc[virq].handle_irq的操作:
    a. 取出irq_desc[virq].action链表中的每一个handler, 执行它
    b. 使用irq_desc[virq].irq_data.chip的函数清中断
    
    如果该中断是由子中断产生, irq_desc[virq].handle_irq的操作:
    a. 读 sub int controller, 得到hwirq'
    b. 根据hwirq'得到virq
    c. 调用 irq_desc[virq].handle_irq
    
    
    
    s3c24xx_handle_intc
        pnd = readl_relaxed(intc->reg_intpnd);
        handle_domain_irq(intc->domain, intc_offset + offset, regs);
            __handle_domain_irq(domain, hwirq, true, regs);
                irq = irq_find_mapping(domain, hwirq);
                generic_handle_irq(irq);
                    struct irq_desc *desc = irq_to_desc(irq);
                    generic_handle_irq_desc(desc);
                        desc->handle_irq(desc);
    
    中断的演变
    
    以前中断号(virq)跟硬件密切相关,
    现在的趋势是中断号跟硬件无关, 仅仅是一个标号而已
    
    
    以前, 对于每一个硬件中断(hwirq)都预先确定它的中断号(virq),
    这些中断号一般都写在一个头文件里, 比如archarmmach-s3c24xxincludemachirqs.h
    使用时,
    a. 执行 request_irq(virq, my_handler) :
       内核根据virq可以知道对应的硬件中断, 然后去设置、使能中断等
    b. 发生硬件中断时,
       内核读取硬件信息, 确定hwirq, 反算出virq,
       然后调用 irq_desc[virq].handle_irq, 最终会用到my_handler
       
       
       
       
    怎么根据hwirq计算出virq?   
    硬件上有多个intc(中断控制器), 
    对于同一个hwirq数值, 会对应不同的virq
    所以在讲hwirq时,应该强调"是哪一个intc的hwirq",
    在描述hwirq转换为virq时, 引入一个概念: irq_domain, 域, 在这个域里hwirq转换为某一个virq
    
    
    
    当中断控制器越来越多、当中断越来越多,上述方法(virq和hwirq固定绑定)有缺陷:
    a. 增加工作量, 你需要给每一个中断确定它的中断号, 写出对应的宏, 可能有成百上千个
    b. 你要确保每一个硬件中断对应的中断号互不重复
    
    有什么方法改进?
    a. hwirq跟virq之间不再绑定
    b. 要使用某个hwirq时, 
       先在irq_desc数组中找到一个空闲项, 它的位置就是virq
       再在irq_desc[virq]中放置处理函数
    
    新中断体系中, 怎么使用中断:
    a.以前是request_irq发起,
      现在是先在设备树文件中声明想使用哪一个中断(哪一个中断控制器下的哪一个中断)
    
    b. 内核解析设备树时,
       会根据"中断控制器"确定irq_domain,
       根据"哪一个中断"确定hwirq, 
       然后在irq_desc数组中找出一个空闲项, 它的位置就是virq
       并且把virq和hwirq的关系保存在irq_domain中: irq_domain.linear_revmap[hwirq] = virq;
    
    c. 驱动程序 request_irq(virq, my_handler)
    
    d. 发生硬件中断时,
    内核读取硬件信息, 确定hwirq,确定中断控制器的域, 确定 virq =  irq_domain.linear_revmap[hwirq];
    然后调用 irq_desc[virq].handle_irq, 最终会用到my_handler
    
    假设要使用子中断控制器(subintc)的n号中断, 它发生时会导致父中断控制器(intc)的m号中断:
    a. 设备树表明要使用<subintc n>
       subintc表示要使用<intc m>
    b. 解析设备树时,
       会为<subintc n>找到空闲项 irq_desc[virq'], sub irq_domain.linear_revmap[n] = virq';
       
       会为<intc m>   找到空闲项 irq_desc[virq], irq_domain.linear_revmap[m] = virq;
       并且设置它的handle_irq为某个分析函数demux_func
    
    c. 驱动程序 request_irq(virq', my_handler)
    
    d. 发生硬件中断时,
    内核读取intc硬件信息, 确定hwirq = m, 确定 virq =  irq_domain.linear_revmap[m];
    然后调用 irq_desc[m].handle_irq, 即demux_func(分发函数)
    
    e. demux_func:
    读取sub intc硬件信息, 确定hwirq = n, 确定 virq' =  sub irq_domain.linear_revmap[n];
    然后调用 irq_desc[n].handle_irq, 即my_handler

    驱动源码 int_key_drv.c :

    #include<linux/module.h>
    #include<linux/kernel.h>
    #include<linux/fs.h>
    #include<linux/init.h>
    #include<linux/delay.h>
    #include<asm/uaccess.h>
    #include<asm/irq.h>
    #include<asm/io.h>
    #include<asm/arch/regs-gpio.h>
    #include<asm/hardware.h>
    #include <linux/irq.h>
    
    
    static struct class *key_int_class;
    static struct class_device *key_int_class_device;
    
    volatile  unsigned long *GPFCON=NULL;
    volatile  unsigned long *GPFDAT=NULL;
    volatile  unsigned long *GPGCON=NULL;
    volatile  unsigned long *GPGDAT=NULL;
    
    
    struct  pin_desc     
    {
       unsigned int pin;
       unsigned int key_val;
    };
    
    static unsigned char key_val;
    static volatile int ev_press=0;
    
    static struct pin_desc pin_desc_array[4]={{S3C2410_GPF0,0x01},{S3C2410_GPF2,0X02},{S3C2410_GPG3,0x03},{S3C2410_GPG11,0x04}};
    
    
    static DECLARE_WAIT_QUEUE_HEAD(wait_key);
    
    static irqreturn_t key_handler(int irq, void *dev_id)
    {
         struct pin_desc *pindesc = (struct pin_desc *)dev_id;
         unsigned int pinval=0;
    
          pinval = s3c2410_gpio_getpin(pindesc->pin);
    
          if(pinval)   key_val = 0x08 | pindesc->key_val; 
              else       key_val =  pindesc->key_val;
    
          
          wake_up_interruptible(&wait_key);
          ev_press = 1;
          return IRQ_RETVAL(IRQ_HANDLED);
    }
    
    
    
    static int key_drv_open(struct inode *inode,struct file *file)
    {
          request_irq(IRQ_EINT0, key_handler,IRQT_BOTHEDGE,"KEY1", &pin_desc_array[0]);
          request_irq(IRQ_EINT2, key_handler,IRQT_BOTHEDGE,"KEY2", &pin_desc_array[1]);
          request_irq(IRQ_EINT11, key_handler,IRQT_BOTHEDGE,"KEY3",&pin_desc_array[2]);
          request_irq(IRQ_EINT19, key_handler,IRQT_BOTHEDGE,"KEY4",&pin_desc_array[3]);
    
          return 0;
    }
    
     ssize_t key_drv_read(struct file *file,const char __user *buf,size_t count,loff_t *ppos)
    {  
        if (count != 1)
            return -EINVAL;
    
         wait_event_interruptible(wait_key,ev_press);
         ev_press=0;
         copy_to_user(buf,&key_val,1);
         
         return 1;
    }
    
    static int key_drv_close(struct inode *inode,struct file *file)
    
    {
        free_irq(IRQ_EINT0, &pin_desc_array[0]);
        free_irq(IRQ_EINT2, &pin_desc_array[1]);
        free_irq(IRQ_EINT11, &pin_desc_array[2]);
        free_irq(IRQ_EINT19, &pin_desc_array[3]);
    
      return 0;
    }
    
    static struct file_operations key_drv_mode=
    {
        .owner = THIS_MODULE,
        .open = key_drv_open,
        .read = key_drv_read,
        .release = key_drv_close,
    };
    
    int major=0;
    static int key_drv_init(void)
    {
        major = register_chrdev(0,"key_int_drv",&key_drv_mode);  //  /proc/devices
    
        key_int_class = class_create(THIS_MODULE,"key_int_class");
        key_int_class_device = class_device_create(key_int_class,NULL,MKDEV(major,0),NULL,"key_int_drv");  //    /dev/key_int_drv
    
        GPFCON=(volatile unsigned long *)ioremap(0x56000050,16);  
        GPFDAT=GPFCON+1;
        GPGCON=(volatile unsigned long *)ioremap(0x56000060,16);  
        GPGDAT=GPGCON+1;
        
        return 0;
    }
    
    static void key_drv_exit(void)
    {
       unregister_chrdev(major,"key_int_drv");
    
       class_device_unregister(key_int_class_device);
       class_destroy(key_int_class);
       iounmap(GPFCON);
       iounmap(GPGCON);
       
    }
    
    module_init(key_drv_init);
    module_exit(key_drv_exit);
    MODULE_LICENSE("GPL");

    测试应用程序 int_key_drv_test.c :

    #include<sys/types.h>
    #include<sys/stat.h>
    #include<fcntl.h>
    #include<stdio.h>
    
    int main(int argc,char **argv)
    {
       int fd=0;
       unsigned char key_val=0;
    
       fd = open("/dev/key_int_drv",O_RDWR);
       if(fd <0) printf("error: can't open device :/dev/key_int_drv");
    
       while(1)
           {        
          read(fd,&key_val,1);  
          printf("key_val=%d
    ",key_val);
       }
    return 0;
    }

    Makefile文件:

    KER_DIR=/work/systems/kernel/linux-2/linux-2.6.22.6
     
    all:
        make -C $(KER_DIR) M=`pwd` modules
    
    clean:
        make -C $(KER_DIR) M=`pwd` modules clean
        rm -fr moudles.order
    
    obj-m +=key_int_drv.o
  • 相关阅读:
    Oracle 用 sqlprompt 修改 sqlplus 提示符
    Oracle cursor pin S wait on X 等待事件 说明
    Oracle 利用 rowid 提升 update 性能
    Oracle 从缓存里面查找真实的执行计划
    Linux 进程状态 说明
    Oracle 用户 对 表空间 配额(quota ) 说明
    Upon startup of Linux database get ORA27102: out of memory LinuxX86_64 Error: 28: No space left on device
    异常宕机 Ora00600 [Kccpb_sanity_check_2] 错误解决方法
    Oracle ORA04031 错误 说明
    Oracle Shared pool 详解
  • 原文地址:https://www.cnblogs.com/zsy12138/p/10397245.html
Copyright © 2020-2023  润新知