内核中断机制

内核版本: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
原文地址:https://www.cnblogs.com/zsy12138/p/10397245.html