LINUX系统中断处理结构及中断函数的实现

中断系统流程解析:
asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
     handle_IRQ(irq, regs);
generic_handle_irq(irq);/*Garmen:进行一般的中断处理*/
      struct irq_desc *desc = irq_to_desc(irq);  /*#define irq_to_desc(irq)    (&irq_desc[irq])       Garmen:他是以irq为下标的一个全局数组项*/
              generic_handle_irq_desc(irq, desc);                 
                     desc->handle_irq(irq, desc);  /*Garmen : 那么究竟是谁调用handle_irq ???  下面进行分析*/

---> 问:所以是谁调用handle_irq ?
       答:进行搜索handle_irq然后进入 kernel\irq\Chip.c
               __irq_set_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained,
                        struct irq_desc *desc = irq_get_desc_buslock(irq, &flags, 0); /*Garmen : 以irq为索引,找到一项*/
                        desc->handle_irq = handle;          /*Garmen : 这里进行设置,把上面以irq为索引找到的那项的handle_irq指定为传进来的参数handle*/
              
               然后我们再搜索__irq_set_handler被谁调用:
irq_set_handler(unsigned int irq, irq_flow_handler_t handle)
    __irq_set_handler(irq, handle, 0, NULL);
        
               而且我们在linux-3.4.2\arch\arm\plat-s3c24xx\Irq.c 发现:
                                   void __init s3c24xx_init_irq(void)     /*Garmen: 其实下面这些就相当于初始化函数*/
                                        for ( irqno = IRQ_EINT0; irqno <= IRQ_EINT3; irqno++)
                                         irq_set_chip_and_handler( irqno, &s3c_irq_eint0t4, handle_edge_irq);     /*Garmen : 再跟进去*/
                                                  /*Garmen:irqno对应的就是上面irq_desc的irq索引
                                                    *然后你会惊人的发现这个 handle_edge_irq 就是handle就是上面 desc->handle_irq = handle 中断处理函数
                                                    */
                                                 irq_set_chip_and_handler_name(irq, chip, handle, NULL);     
                                                            irq_set_chip(irq, chip);
                                                                      desc->irq_data.chip = chip;   /*Garmen:chip函数也是一样,有跟进来的irq索引和chip*/
                                                           __irq_set_handler(irq, handle, 0, name);     /*Garmen : 于是我们惊人的发现这个函数被调用了*/

再研究一下handle_edge_irq
handle_edge_irq:
     /* Start handling the irq */
     desc->irq_data.chip->irq_ack(&desc->irq_data);

     handle_irq_event(desc);
              struct irqaction *action = desc->action;      /*Garmen : 取出desc中action成员*/
              ret = handle_irq_event_percpu(desc, action);          /*Garemn : 执行action相关函数*/




小结:问:按下按键之后怎么处理
         答:1、进入异常模式:
                         b vector_irq + 偏移值
               2、调用列表,比如调用到irq_usr,保持现场什么的工作
               3、调用asm_do_irq 
               4、调用irq_desc[irq]->handle_irq /*Garmen: 以中断号为下标,取出一项处理函数*/
               5、上面的handle_irq 就是一个中断处理函数 , 好比如handle_edge_irq
                    里面做了什么事情呢?参考上面 handle_edge_irq的研究




irq_desc结构体定义及其说明: 他是一个全局数组

①: chip是一些芯片底层硬件相关的操作(比如响应中断、清中断等等),在linux3.4.2内核。他在irq_data这个数组里面,这里没有列出来
②: handle_irq是中断处理函数,将aciton链表中的成员一一取出来,然后执行action->handler

/**
 * struct irq_desc - interrupt descriptor
 * @irq_data:        per irq and chip data passed down to chip functions
 * @timer_rand_state:    pointer to timer rand state struct
 * @kstat_irqs:        irq stats per cpu
 * @handle_irq:        highlevel irq-events handler
 * @preflow_handler:    handler called before the flow handler (currently used by sparc)
 * @action:        the irq action chain
 * @status:        status information
 * @core_internal_state__do_not_mess_with_it: core internal status information
 * @depth:        disable-depth, for nested irq_disable() calls
 * @wake_depth:        enable depth, for multiple irq_set_irq_wake() callers
 * @irq_count:        stats field to detect stalled irqs
 * @last_unhandled:    aging timer for unhandled count
 * @irqs_unhandled:    stats field for spurious unhandled interrupts
 * @lock:        locking for SMP
 * @affinity_hint:    hint to user space for preferred irq affinity
 * @affinity_notify:    context for notification of affinity changes
 * @pending_mask:    pending rebalanced interrupts
 * @threads_oneshot:    bitfield to handle shared oneshot threads
 * @threads_active:    number of irqaction threads currently running
 * @wait_for_threads:    wait queue for sync_irq to wait for threaded handlers
 * @dir:        /proc/irq/ procfs entry
 * @name:        flow handler name for /proc/interrupts output
 */
struct irq_desc {
    struct irq_data        irq_data;
    struct timer_rand_state *timer_rand_state;
    unsigned int __percpu    *kstat_irqs;
    irq_flow_handler_t    handle_irq;
#ifdef CONFIG_IRQ_PREFLOW_FASTEOI
    irq_preflow_handler_t    preflow_handler;
#endif
    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        irq_count;    /* For detecting broken IRQs */
    unsigned long        last_unhandled;    /* Aging timer for unhandled count */
    unsigned int        irqs_unhandled;
    raw_spinlock_t        lock;
    struct cpumask        *percpu_enabled;
#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_PROC_FS
    struct proc_dir_entry    *dir;
#endif
    struct module        *owner;
    const char        *name;
} ____cacheline_internodealigned_in_smp;





struct irqaction {
    irq_handler_t        handler;
    unsigned long        flags;
    void            *dev_id;
    void __percpu        *percpu_dev_id;
    struct irqaction    *next;
    int            irq;
    irq_handler_t        thread_fn;
    struct task_struct    *thread;
    unsigned long        thread_flags;
    unsigned long        thread_mask;
    const char        *name;
    struct proc_dir_entry    *dir;
} ____cacheline_internodealigned_in_smp;





分析request_irq函数
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long irqflags, const char *devname, void *dev_id)
    return request_threaded_irq(irq, handler, NULL, flags, name, dev);
          int request_threaded_irq(unsigned int irq, irq_handler_t handler, irq_handler_t thread_fn, unsigned long irqflags, const char *devname, void *dev_id)
               struct irqaction *action;
               struct irq_desc *desc;


action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);     /*Garmen : 分配设置了action结构*/
if (!action)
        return -ENOMEM;
action->handler = handler;
action->thread_fn = thread_fn;
action->flags = irqflags;
action->name = devname;
action->dev_id = dev_id;

       desc = irq_to_desc(irq);     /*Garmen : 以中断号为下标找到了这个desc全局数组项,将这个desc传递给下面的setup_irq */

                   retval = __setup_irq(irq, desc, action);     /*Garmen : 然后调用__setup_irq 设置中断*/
                        struct irqaction *old, **old_ptr; /*定义old指针做判断*/
                        old_ptr = &desc->action;
                        old = *old_ptr
                        /*Garmen : 你知道什么是共享中断吗?*/
                        ...
                        /*Garmen : 对action做一系列判断,比如是否是共享中断啊等等,假如不是,就将我们传进来的action加入aciton链表(具体看上面的图)
                         *然后desc->chip->settype       将引脚配置中断引脚,还有那些flag什么进行定义
                         *然后desc->->startup/enable    使能引脚
                         */
                         
                        ...


插一小部分相关操作
打开设备: exec 5</dev/buttons     /*Garmen : 打开这个设备,把它定位到5去*/
查看设备: ps知道当前设备 -sh是771 然后 ls -l /proc/771/fd
关闭设备: exec 5<&-
删除进程:kill -0 PID号
退出进程:kill -9 PID号 




1、中断申请函数:
     request_irq(IRQ_EINT0,  buttons_irq, IRQT_BOTHEDGE, "S2", &pins_desc[0]);
     函数原型:
     int request_irq(unsigned int irq, irq_handler_t handler, unsigned long irqflags, const char *devname, void *dev_id)
     第一个参数:unsigned int irq 可例 : IRQ_EINT0
                        在Irq.h进行宏定义(include\asm-arm)
                        在Irq.c 的s3c24xx_init_irq函数中有进行初始化;
                        中断号是去Irq.h 将该宏展开得到的一个数字
     第二个参数: irq_handler_t handler 可例 : buttons_irq 是一个处理函数
     第三个参数:unsigned long irqflags 可例:IRQT_BOTHEDGE 双边缘触发
                        在Irq.h进行宏定义
                        在Irq.c 的s3c_irqext_type中进行初始化
     第四个参数:随意
     第五个参数:void *dev_id 
                        它是void型的指针。所以不必顾虑太多;
                        它只不过是用来在free_irq卸载时候,与fre_irq的参数结合在一起,确定卸载哪一个irq_action 结构

小结:当发生IRQ_EINT0这个中断的时候,就去调用这个buttons_irq中断处理函数,
          这个函数static irqreturn_t buttons_irq(int irq, void *dev_id),有两个参数
          第一个参数是中断号等于IRQ_EINT0 ,第二个参数是dev_id 等于pins_desc[0]

敲黑板:
     后面引入这样的一个方法:将irq、*name、pin、key_val写成一个结构体dev_id
     例如

定义一个pin_desc结构体原型:
struct pin_desc{
    int irq;
    char *name;
    unsigned int pin;
    unsigned int key_val;
};
编写我们自己想要的结构体:
struct pin_desc pins_desc[4] = {
    {IRQ_EINT0,  "S2", S3C2410_GPF0,   KEY_L},
    {IRQ_EINT2,  "S3", S3C2410_GPF2,   KEY_S},
    {IRQ_EINT11, "S4", S3C2410_GPG3,   KEY_ENTER},
    {IRQ_EINT19, "S5",  S3C2410_GPG11, KEY_LEFTSHIFT},
};
我们的注册函数就可以这么写了:
    for (i = 0; i < 4; i++)
    {
        request_irq(pins_desc[i].irq, buttons_irq, IRQT_BOTHEDGE, pins_desc[i].name, &pins_desc[i]);
    }


                         

     里面所做的事情:
     ①:分配一个irqaction
     ②:把这个结构体放入irq_des[irq] <==action链表
     ③:设置引脚
     ④:中断使能

     
2、中断清除函数
     free_irq(irq, dev_id)
     函数原型:void free_irq(unsigned int irq, void *dev_id)
     里面所做的事情
     ①:出链
     ②:禁止中断

3、系统函数:s3c2410_gpio_getpin
     如何使用:
          直接拿GPIO口来判断就行!
          if (s3c2410_gpio_getpin (s3c2410_GPG10) == 0)
     对寄存器的读取和判断
          
4、休眠函数!
休眠函数:
     函数原型:
#define wait_event_interruptible(wq, condition)                \
({                                    \
    int __ret = 0;                            \
    if (!(condition))                        \
        __wait_event_interruptible(wq, condition, __ret);    \
    __ret;                                \
})

     里面所做的事情:
          先判断condition,假如condition为0的话,就调用__wait_event_interruptible(wq, condition, __ret);  让应用程序进行休眠;
          假如!condition,不等于0的话,函数就跳过wait_event_interruptible,向下运行__wait_event_interruptible了!!!
          所以我们运用该休眠函数的时候,要在wait_event_interruptible 后面,将condition标志位置1;
     例函:  
     /* 中断事件标志, 中断服务程序将它置1,forth_drv_read将它清0 */
     wait_event_interruptible(button_waitq, ev_press);

     还要定义static DECLARE_WAIT_QUEUE_HEAD(button_waitq);     
          意思是:定义一个等待队列头结构体;
     
唤醒函数:
    ev_press = 1;                Garmen: 表示中断发生了 
    wake_up_interruptible(&button_waitq);   Garmen::唤醒休眠的进程 


参考驱动函数:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>


static struct class *thirddrv_class;
static struct class_device    *thirddrv_class_dev;

volatile unsigned long *gpfcon;
volatile unsigned long *gpfdat;

volatile unsigned long *gpgcon;
volatile unsigned long *gpgdat;

/*Garmen : 中断队列头*/
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);

/* 中断事件标志, 中断服务程序将它置1,third_drv_read将它清0 */
static volatile int ev_press = 0;


struct pin_desc{
    unsigned int pin;
    unsigned int key_val;
};


/* 键值: 按下时, 0x01, 0x02, 0x03, 0x04 */
/* 键值: 松开时, 0x81, 0x82, 0x83, 0x84 */
static unsigned char key_val;

struct pin_desc pins_desc[4] = {
    {S3C2410_GPF0, 0x01},
    {S3C2410_GPF2, 0x02},
    {S3C2410_GPG3, 0x03},
    {S3C2410_GPG11, 0x04},
};


/*
  * 确定按键值
  */
static irqreturn_t buttons_irq(int irq, void *dev_id)
{
    struct pin_desc * pindesc = (struct pin_desc *)dev_id;
    unsigned int pinval;

    pinval = s3c2410_gpio_getpin(pindesc->pin);

    if (pinval)
    {
        /* 松开 */
        key_val = 0x80 | pindesc->key_val;
    }
    else
    {
        /* 按下 */
        key_val = pindesc->key_val;
    }

    ev_press = 1;                  /* 表示中断发生了 */
    wake_up_interruptible(&button_waitq);   /* 唤醒休眠的进程 */


    return IRQ_RETVAL(IRQ_HANDLED);
}

static int third_drv_open(struct inode *inode, struct file *file)
{
    /* 配置GPF0,2为输入引脚 */
    /* 配置GPG3,11为输入引脚 */
    request_irq(IRQ_EINT0,  buttons_irq, IRQT_BOTHEDGE, "S2", &pins_desc[0]);
    request_irq(IRQ_EINT2,  buttons_irq, IRQT_BOTHEDGE, "S3", &pins_desc[1]);
    request_irq(IRQ_EINT11, buttons_irq, IRQT_BOTHEDGE, "S4", &pins_desc[2]);
    request_irq(IRQ_EINT19, buttons_irq, IRQT_BOTHEDGE, "S5", &pins_desc[3]);   

    return 0;
}

ssize_t third_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
    if (size != 1)
        return -EINVAL;

    /* 如果没有按键动作, 休眠 */
    wait_event_interruptible(button_waitq, ev_press);

    /* 如果有按键动作, 返回键值 */
    copy_to_user(buf, &key_val, 1);

    /*Garmen : 同时将休眠标志位至0 这样又可以继续休眠*/
    ev_press = 0;

    return 1;
}


int third_drv_close(struct inode *inode, struct file *file)
{
    free_irq(IRQ_EINT0, &pins_desc[0]);
    free_irq(IRQ_EINT2, &pins_desc[1]);
    free_irq(IRQ_EINT11, &pins_desc[2]);
    free_irq(IRQ_EINT19, &pins_desc[3]);
    return 0;
}


static struct file_operations sencod_drv_fops = {
    .owner    =  THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
    .open     =  third_drv_open,     
    .read     =    third_drv_read,       
    .release  =  third_drv_close,       
};


int major;
static int third_drv_init(void)
{
    major = register_chrdev(0, "third_drv", &sencod_drv_fops);

    thirddrv_class = class_create(THIS_MODULE, "third_drv");

    thirddrv_class_dev = class_device_create(thirddrv_class, NULL, MKDEV(major, 0), NULL, "buttons"); /* /dev/buttons */

    gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
    gpfdat = gpfcon + 1;

    gpgcon = (volatile unsigned long *)ioremap(0x56000060, 16);
    gpgdat = gpgcon + 1;

    return 0;
}

static void third_drv_exit(void)
{
    unregister_chrdev(major, "third_drv");
    class_device_unregister(thirddrv_class_dev);
    class_destroy(thirddrv_class);
    iounmap(gpfcon);
    iounmap(gpgcon);
    return 0;
}


module_init(third_drv_init);

module_exit(third_drv_exit);

MODULE_LICENSE("GPL");





在测试程序中调用:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>

/* thirddrvtest
  */
int main(int argc, char **argv)
{
    int fd;
    unsigned char key_val;

    fd = open("/dev/buttons", O_RDWR);
    if (fd < 0)
    {
        printf("can't open!\n");
    }

    while (1)
    {
        read(fd, &key_val, 1);
        printf("key_val = 0x%x\n", key_val);
    }

    return 0;
}