linux驱动2.1按键中断-中断过程分析和程序编写

一、分析中断运行过程

本节目标:
  分析在linux中的中断是如何运行的,以及中断3大结构体:irq_desc、irq_chip、irqaction

1.1、裸板中断运行过程

在裸板程序中(参考stmdb和ldmia详解:https://www.cnblogs.com/lifexy/p/7363208.html):
1)按键按下,
2)cpu发生中断,
3)强制跳到异常向量入口执行(0x18中断地址处)
3.1)使用stmdb将寄存器值保存在栈顶(保护现场)

stmdb sp!, { r0-r12,lr }

3.2)执行中断服务函数
3.3)使用ldmia将栈顶处数据读出到寄存器中,并使pc=lr(恢复现场)

ldmia  sp!, { r0-r12,pc }^
//^表示将spsr的值复制到cpsr,因为异常返回后需要恢复异常发生前的工作状态

1.2、linux中断运行过程

在linux中:
  需要先设置异常向量地址(参考linux应用手册P412):
  在ARM裸板中异常向量基地址是0x00000000,如下图:
1740228-20190925103855588-118841829.png
  而linux内核中异常向量基地址是0xffff0000(虚拟地址),位于代码arch/cam/kernel/traps.c,代码如下:

void __init trap_init(void)
{         
    /* CONFIG_VECTORS_BASE :内核配置项,在.config文件中,设置的是0Xffff0000*/
    /* vectors =0xffff0000*/
    unsigned long vectors = CONFIG_VECTORS_BASE;
...
 
    /*将异常向量地址复制到0xffff0000处*/
    memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
    memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
    memcpy((void *)vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz);
...
}

  上面代码中主要是将__vectors_end - __vectors_start之间的代码复制到vectors (0xffff0000)处,
  问:__vectors_start为什么是异常向量基地址?
  答:通过搜索,找到它在arch/arm/kernel/entry_armv.S中定义:

__vectors_start:
         swi    SYS_ERROR0                      //复位异常,复位时会执行
         b       vector_und + stubs_offset              //undefine未定义指令异常
         ldr     pc, .LCvswi + stubs_offset             //swi软件中断异常 
         b       vector_pabt + stubs_offset             //指令预取中止abort
         b       vector_dabt + stubs_offset             //数据访问中止abort
         b       vector_addrexcptn + stubs_offset       //没有用到
         b       vector_irq + stubs_offset             //irq异常
         b       vector_fiq + stubs_offset            //fig异常

  其中stubs_offset是链接地址的偏移地址, vector_und、vector_pabt等表示要跳转去执行的代码
1)问:以vector_irq中断为例, vector_irq是个宏,它在哪里定义呢?
  答:它还是在arch/arm/kernel/entry_armv.S中定义,如下所示:

vector_stub  irq, IRQ_MODE, 4//irq:名字  IRQ_MODE:0X12    4:偏移量

  上面的vector_stub 根据参数irq, IRQ_MODE, 4来定义” vector_ irq”这个宏(其它宏也是这样定义的)
2)问:vector_stub又是怎么实现出来的定义不同的宏呢?
  答:我们找到vector_stub这个定义:

.macro    vector_stub, name, mode, correction=0  //定义vector_stub有3个参数
.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                       //读出spsr
         str     lr, [sp, #8]           @ save spsr

 
         @
         @ Prepare for SVC32 mode.  IRQs remain disabled.
         @ 进入管理模式
         mrs   r0, cpsr                    //读出cpsr
         eor    r0, r0, #(\mode ^ SVC_MODE)  
         msr   spsr_cxsf, r0

         @
         @ the branch table must immediately follow this code
         @
         and    lr, lr, #0x0f    //lr等于进入模式之前的spsr,&0X0F就等于模式位
         mov  r0, sp
         ldr     lr, [pc, lr, lsl #2]  
         movs pc, lr                    @ branch to handler in SVC mode

3)因此我们将上面__vectors_start里的b vector_irq + stubs_offset 中断展开如下:

.macro    vector_stub, name, mode, correction=0  //定义vector_stub有3个参数
.align      5
  vector_stub  irq, IRQ_MODE, 4   //这三个参数值代入 vector_stub中

vector_ irq:                   //定义 vector_ irq
  /*计算返回地址(在arm流水线中,lr=pc+8,但是pc+4只译码没有执行,所以lr=lr-4) */
         sub    lr, lr, #4             
        
         @
         @ Save r0, lr_<exception> (parent PC) and spsr_<exception>
         @ (parent CPSR)
         @保存r0和lr和spsr
         stmia sp, {r0, lr}               //存入sp栈里  
         mrs   lr, spsr                       //读出spsr
         str     lr, [sp, #8]           @ save spsr

 

         @
         @ Prepare for SVC32 mode.  IRQs remain disabled.
         @ 进入管理模式
         mrs   r0, cpsr                    //读出cpsr
         eor    r0, r0, #(\mode ^ SVC_MODE)  
         msr   spsr_cxsf, r0

         @
         @ the branch table must immediately follow this code
         @
         and    lr, lr, #0x0f    //lr等于进入模式之前的spsr,&0X0F就等于模式位
         mov  r0, sp
         ldr     lr, [pc, lr, lsl #2]   //如果进入中断前是usr,则取出PC+4*0的内容,即__irq_usr @如果进入中断前是svc,则取出PC+4*3的内容,即__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等位置。

4)我们先选择__irq_usr作为下一步跟踪的目标:
4.1)其中__irq_usr的实现如下(arch\arm\kernel\entry-armv.S):

__irq_usr:
  usr_entry                     //保存数据到栈里
  get_thread_info tsk 
  irq_handler                     //调用irq_handler
  b ret_to_user

4.2)irq_handler的实现过程,arch\arm\kernel\entry-armv.S

.macro  irq_handler
  get_irqnr_preamble r5, lr 
  get_irqnr_and_base r0, r6, r5, lr         // get_irqnr_and_base:获取中断号,r0=中断号
  movne        r1, sp                 //r1等于sp  (发生中断之前的各个寄存器的基地址) 
  adrne lr, 1b
  bne    asm_do_IRQ                   //调用asm_do_IRQ, irq=r0   regs=r1

  irq_handler最终调用asm_do_IRQ
4.3)asm_do_IRQ实现过程,arch/arm/kernel/irq.c
  该函数和裸板中断处理一样的,完成3件事情:

  • 分辨是哪个中断;
  • 通过desc_handle_irq(irq, desc)调用对应的中断处理函数;
  • 清中断
asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)  //irq:中断号   *regs:发生中断前的各个寄存器基地址
{
    struct pt_regs *old_regs = set_irq_regs(regs);
/*根据irq中断号,找到哪个中断, *desc =irq_desc[irq]*/
    struct irq_desc *desc = irq_desc + irq; // irq_desc是个数组(位于kernel/irq/handle.c)
 
    if (irq >= NR_IRQS) 
        desc = &bad_irq_desc;
 
    irq_enter();    
    desc_handle_irq(irq, desc);     // desc_handle_irq根据中断号和desc,调用函数指针,进入中断处理,

    irq_finish(irq);
    irq_exit();
    set_irq_regs(old_regs);
}

  上面主要是执行desc_handle_irq函数进入中断处理
  其中desc_handle_irq代码如下:

desc->handle_irq(irq, desc);//相当于执行irq_desc[irq]-> handle_irq(irq, irq_desc[irq]);

  它会执行handle_irq成员函数,这个成员handle_irq又是在哪里被赋值的?
  搜索handle_irq,找到它位于kernel/irq/chip.c,__set_irq_handler函数下:

void  __set_irq_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained,const char *name)
{
  ... ...
  desc = irq_desc + irq;      //在irq_desc结构体数组中找到对应的中断
  ... ...
  desc->handle_irq = handle;  //使handle_irq成员指向handle参数函数
}

  继续搜索__set_irq_handler函数,它被set_irq_handler函数调用:

static inline void set_irq_handler(unsigned int irq, irq_flow_handler_t handle)
{
         __set_irq_handler(irq, handle, 0, NULL);
}

  继续搜索set_irq_handler函数,如下图
1740228-20190925114316175-864382244.png
  发现它在s3c24xx_init_irq(void)函数中被多次使用,显然在中断初始化时,多次进入__set_irq_handler函数,并在irq_desc数组中构造了很多项 handle_irq函数
  我们来看看irq_desc中断描述结构体到底有什么内容:

struct irq_desc {
         irq_flow_handler_t       handle_irq;  //指向中断函数, 中断产生后,就会执行这个handle_irq
         struct irq_chip   *chip; //指向irq_chip结构体,用于底层的硬件访问,下面会介绍
         struct msi_desc             *msi_desc; 
         void                     *handler_data;  
         void                     *chip_data;
         struct irqaction     *action;      /* IRQ action list */   //action链表,用于中断处理函数
         unsigned int                  status;                  /* IRQ status */
         unsigned int                  depth;                  /* nested irq disables */
         unsigned int                  wake_depth;        /* nested wake enables */
         unsigned int                  irq_count;   /* For detecting broken IRQs */
         unsigned int                  irqs_unhandled;
         spinlock_t            lock;          
     ... ...
         const char            *name;              //产生中断的硬件名字
} ;

  其中的成员*chip的结构体,用于底层的硬件访问, irq_chip类型如下:

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);   //开启中断源
         ... ...
     int              (*set_type)(unsigned int irq, unsigned int flow_type);  //将对应的引脚设置为中断类型的引脚
     ... ...
#ifdef CONFIG_IRQ_RELEASE_METHOD
         void            (*release)(unsigned int irq, void *dev_id);       //释放中断服务函数
#endif
};

  其中的成员struct irqaction *action,主要是用来存用户注册的中断处理函数,
  一个中断可以有多个处理函数 ,当一个中断有多个处理函数,说明这个是共享中断.
  所谓共享中断就是一个中断的来源有很多,这些来源共享同一个引脚。
  所以在irq_desc结构体中的action成员是个链表,以action为表头,若是一个以上的链表就是共享中断
irqaction结构定义如下:

struct irqaction {
         irq_handler_t handler;      //等于用户注册的中断处理函数,中断发生时就会运行这个中断处理函数
         unsigned long flags;         //中断标志,注册时设置,比如上升沿中断,下降沿中断等
         cpumask_t mask;           //中断掩码
         const char *name;          //中断名称,产生中断的硬件的名字
         void *dev_id;              //设备id
         struct irqaction *next;        //指向下一个成员
         int irq;                    //中断号,
         struct proc_dir_entry *dir;    //指向IRQn相关的/proc/irq/
};

  上面3个结构体的关系如下图所示:
1740228-20190925114450654-1912558080.png
  我们来看看s3c24xx_init_irq()函数是怎么初始化中断的,以外部中断0为例(位于s3c24xx_init_irq函数):
  s3c24xx_init_irq()函数中部分代码如下:

/*其中IRQ_EINT0=16, 所以irqno=16 */
for (irqno = IRQ_EINT0; irqno <= IRQ_EINT3; irqno++) 
{
 irqdbf("registering irq %d (ext int)\n", irqno);

 /*在set_irq_chip函数中会执行:
     desc = irq_desc + irq;
     desc->chip = chip;*/
 set_irq_chip(irqno, &s3c_irq_eint0t4);  //所以(irq_desc+16)->chip= &s3c_irq_eint0t4

 /* set_irq_handler 会调用__set_irq_handler 函数*/
set_irq_handler(irqno, handle_edge_irq); //所以(irq_desc+16)-> handle_irq = handle_edge_irq

set_irq_flags(irqno, IRQF_VALID);
}

  初始化了外部中断0后,当外部中断0触发,就会进入我们之前分析的asm_do_IRQ函数中,调用(irq_desc+16)-> handle_irq也就是handle_edge_irq函数。
  我们来分析下handle_edge_irq函数是如何执行中断服务的:

void fastcall handle_edge_irq(unsigned int irq, struct irq_desc *desc)
{
    const unsigned int cpu = smp_processor_id();
    spin_lock(&desc->lock);
    desc->status &= ~(IRQ_REPLAY | IRQ_WAITING);


    /*判断这个中断是否正在运行(INPROGRESS)或者禁止(DISABLED)*/
    if (unlikely((desc->status & (IRQ_INPROGRESS | IRQ_DISABLED)) || !desc->action)) 
    {
        desc->status |= (IRQ_PENDING | IRQ_MASKED);
        mask_ack_irq(desc, irq);       //屏蔽中断
        goto out_unlock;
    }

    kstat_cpu(cpu).irqs[irq]++;   //计数中断次数

    /* Start handling the irq */     
    desc->chip->ack(irq);    //开始处理这个中断
 
    /* Mark the IRQ currently in progress.*/
    desc->status |= IRQ_INPROGRESS;      //标记当前中断正在运行   
    do {
        struct irqaction *action = desc->action;
        irqreturn_t action_ret;
 
        if (unlikely(!action)) {             //判断链表是否为空
            desc->chip->mask(irq);
            goto out_unlock;
        }

        if (unlikely((desc->status &
             (IRQ_PENDING | IRQ_MASKED | IRQ_DISABLED)) ==
            (IRQ_PENDING | IRQ_MASKED))) {
            desc->chip->unmask(irq);
            desc->status &= ~IRQ_MASKED;
        }

        desc->status &= ~IRQ_PENDING;
        spin_unlock(&desc->lock);
        action_ret = handle_IRQ_event(irq, action);   //真正的处理过程
        if (!noirqdebug)
            note_interrupt(irq, desc, action_ret);
        spin_lock(&desc->lock);
    } while ((desc->status & (IRQ_PENDING | IRQ_DISABLED)) == IRQ_PENDING);

    desc->status &= ~IRQ_INPROGRESS;

out_unlock:
    spin_unlock(&desc->lock);
}

上面handle_edge_irq()函数主要执行了:
1)desc->chip->ack(irq); //开始处理这个中断
  在s3c24xx_init_irq()函数中chip成员指向了s3c_irq_eint0t4(),
  所以desc->chip->ack(irq)就是执行handle_edge_irq(irq)函数,handle_edge_irq函数如下:

s3c_irq_ack(unsigned int irqno)
{
         unsigned long bitval = 1UL << (irqno - IRQ_EINT0); 
         __raw_writel(bitval, S3C2410_SRCPND);    //向SRCPND寄存器写入bitval ,清SRCPND中断
         __raw_writel(bitval, S3C2410_INTPND);   //向INTPND寄存器位写入bitval ,清INTPND中断
}

  所以desc->chip->ack(irq); 主要执行清中断之类的
2)handle_IRQ_event(irq, action); //真正的处理过程
  handle_IRQ_event()代码如下:

handle_IRQ_event(unsigned int irq, struct irqaction *action)
{
    irqreturn_t ret, retval = IRQ_NONE;
    unsigned int status = 0;
    handle_dynamic_tick(action);
    if (!(action->flags & IRQF_DISABLED))
        local_irq_enable_in_hardirq();
    do {
        ret = action->handler(irq, action->dev_id);     //执行action->handler
        if (ret == IRQ_HANDLED)
            status |= action->flags;
        retval |= ret;
        action = action->next;    //指向下个action成员
    } while (action);          //取出action所有成员

    if (status & IRQF_SAMPLE_RANDOM)
        add_interrupt_randomness(irq);
    local_irq_disable();
    return retval;
}

  所以handle_IRQ_event()函数主要是取出action链表中的成员,然后执行irq_desc->action->handler(irq, action->dev_id);
  action链表是irq_desc中断描述符结构体的 成员

1.3、本节常用函数总结

trap_init(): 初始化异常向量的虚拟基地址,一般为0XFFFF0000
s3c24xx_init_irq():初始化各个中断
set_irq_chip(irqno, &s3c_irq_eint0t4):设置irq_desc[irqno]->chip等于第二个参数
set_irq_handler(irqno, handle_edge_irq); 设置irq_desc[irqno]->handle_irq等于第二个参数
asm_do_IRQ():中断产生后,会进入这个函数,最终执行 desc->handle_irq(irq, desc);
handle_edge_irq(irq, desc):执行中断函数,主要是执行以下两步骤:
  1) desc->chip->ack(irq):相应中断,也就是清中断,使能再次接受中断
  2) handle_IRQ_event(irq, action):执行中断的服务函数,desc->action->handler

1.4、中断运行总结

当产生一个中断异常
1)进入异常向量vector,比如中断异常: vector_irq + stubs_offset
2)比如中断异常之前是用户模式(正常工作),则进入__irq_usr,然后最终进入asm_do_IRQ函数,
3)然后执行irq_desc [irq]->handle_irq(irq, irq_desc [irq]);
  通过刚才的分析,外部中断0(irq_desc[16])的handle_irq成员等于handle_edge_irq函数,所以就是执行handle_edge_irq(irq, irq_desc [irq]);
4)以外部中断0为例,在handle_edge_irq函数中主要执行两步:
  ->4.1 desc->chip->ack //使用chip成员中的ack函数来清中断
  ->4.2 执行action链表 irq_desc->action->handler
这4步都是系统给做好的(中断的框架),当我们想自己写个中断处理程序,去执行自己的代码,就需要写irq_desc->action->handler,然后通过request_irq()来向内核申请注册中断

二、分析request_irq(free_irq)函数如何注册(注销)中断

本节目标:
  分析request_irq()如何申请注册中断,free_irq()如何注销中断
  当我们想自己写个中断处理程序,去执行自己的代码,就需要写irq_desc->action->handler,然后通过request_irq()来向内核申请注册中断

2.1、request_irq()

  request_irq()位于kernel/irq/ manage .c,函数原型如下:

int request_irq(unsigned int irq, irq_handler_t handler, unsigned long irqflags, const char *devname, void *dev_id)

参数说明:
  unsigned int irq:为要注册中断服务函数的中断号,比如外部中断0就是16,定义在mach/irqs.h
  irq_handler_t handler:为要注册的中断服务函数,就是(irq_desc+ irq )->action->handler
  unsigned long irqflags: 触发中断的参数,比如边沿触发, 定义在linux/interrupt.h。
  const char devname:中断程序的名字,使用cat /proc/interrupt 可以查看中断程序名字
  void
dev_id:传入中断处理程序的参数,注册共享中断时不能为NULL,因为卸载时需要这个做参数,避免卸载其它中断服务函数
  1)request_irq代码如下:

int request_irq(unsigned int irq, irq_handler_t handler, unsigned long irqflags, const char *devname, void *dev_id)
{
    struct irqaction *action;
...
    action = kmalloc(sizeof(struct irqaction), GFP_ATOMIC);  //注册irqaction结构体类型的action
    if (!action)
        return -ENOMEM;

    /* 将带进来的参数赋给action   */
    action->handler = handler;     
    action->flags = irqflags;
    cpus_clear(action->mask);
    action->name = devname;
    action->next = NULL;
    action->dev_id = dev_id;

    select_smp_affinity(irq);
...
    retval = setup_irq(irq, action);   // 进入setup_irq(irq, action),设置irq_ desc[irq]->action
   
    if (retval)
        kfree(action);

    return retval;
}

  从上面分析,request_irq()函数主要注册了一个irqaction型action,然后把参数都赋给这个action,最后进入setup_irq(irq, action)设置irq_ desc[irq]->action。
  2)看看setup_irq(irq, action)如何设置irq_ desc[irq]->action的:

int setup_irq(unsigned int irq, struct irqaction *new)
{
    struct irq_desc *desc = irq_desc + irq;   //根据中断号找到irq_ desc[irq]
...
    p = &desc->action;                 //指向desc->action
    old = *p;
    if (old) {                 //判断action是否为空
    /*判断这个中断是否支持共享 (IRQF_SHARED)*/
        if (!((old->flags & new->flags) & IRQF_SHARED) ||
            ((old->flags ^ new->flags) & IRQF_TRIGGER_MASK)) {
            old_name = old->name;
            goto mismatch;                  //不支持,则跳转
        }

#if defined(CONFIG_IRQ_PER_CPU)
        /* All handlers must agree on per-cpuness */
        if ((old->flags & IRQF_PERCPU) !=
            (new->flags & IRQF_PERCPU))
            goto mismatch;
#endif

        /*找到action链表尾处,后面用于添加 新的中断服务函数(*new) */
        do {
            p = &old->next;
            old = *p;
        } while (old);
        shared = 1;        //表示该中断支持共享,添加新的action,否则直接赋值新的action
    }

    *p = new;             //指向新的action
 ...
    if (!shared) {                  //若该中断不支持共享
        irq_chip_set_defaults(desc->chip);    //更新desc->chip,将为空的成员设置默认值
#if defined(CONFIG_IRQ_PER_CPU)
        if (new->flags & IRQF_PERCPU)
            desc->status |= IRQ_PER_CPU;
#endif

        /* Setup the type (level, edge polarity) if configured: */
        if (new->flags & IRQF_TRIGGER_MASK) {
            if (desc->chip && desc->chip->set_type)        // desc->chip->set_type设置为中断引脚
                desc->chip->set_type(irq,new->flags & IRQF_TRIGGER_MASK);
            else
                printk(KERN_WARNING "No IRQF_TRIGGER set_type "
                        "function for IRQ %d (%s)\n", irq,
                        desc->chip ? desc->chip->name :
                        "unknown");
        } else
            compat_irq_chip_set_default_handler(desc);

        desc->status &= ~(IRQ_AUTODETECT | IRQ_WAITING |
IRQ_INPROGRESS);

        if (!(desc->status & IRQ_NOAUTOEN)) {
            desc->depth = 0;
            desc->status &= ~IRQ_DISABLED;
            if (desc->chip->startup)
                desc->chip->startup(irq);     //开启中断
            else
                desc->chip->enable(irq);     //使能中断
        } else
            /* Undo nested disables: */
            desc->depth = 1;
    }

  从上面可以看出setup_irq(irq, action)主要是将action中断服务函数放在irq_ desc[irq]->action中。
  然后设置中断引脚:

desc->chip->set_type(irq,new->flags & IRQF_TRIGGER_MASK);

  最后[开启/使能]中断:

desc->chip->[startup(irq) /enable(irq)];     //[开启/使能]中断

  我们以外部中断0的desc[16]->chip->set_type为例,来看看它是如何初始化中断引脚的:

s3c_irqext_type(unsigned int irq, unsigned int type)
{
    void __iomem *extint_reg;
    void __iomem *gpcon_reg;
    unsigned long gpcon_offset, extint_offset;
    unsigned long newvalue = 0, value;
    if ((irq >= IRQ_EINT0) && (irq <= IRQ_EINT3))    //找到寄存器
    {
        gpcon_reg = S3C2410_GPFCON;      
        extint_reg = S3C24XX_EXTINT0;      // EXTINT0对应中断0~中断7
        gpcon_offset = (irq - IRQ_EINT0) * 2;    //找到gpcon寄存器的相应位偏移量
        extint_offset = (irq - IRQ_EINT0) * 4;    //找到extint寄存器的相应位偏移量
    }
    else if(... ...)                    //找到其它的EINT4~23的寄存器

    /*将GPIO引脚设为中断引脚*/
    value = __raw_readl(gpcon_reg);  
    value = (value & ~(3 << gpcon_offset)) | (0x02 << gpcon_offset);  //相应位设置0x02
    switch (type)          //设置EXTINT0中断模式
    {
    case IRQT_NOEDGE:            //未指定的中断模式
        printk(KERN_WARNING "No edge setting!\n");
        break; 

    case IRQT_RISING:           //上升沿触发,设置EXTINT0相应位为0x04
        newvalue = S3C2410_EXTINT_RISEEDGE;
        break;

    case IRQT_FALLING:     //下降沿触发,设置EXTINT0相应位为0x02
        newvalue = S3C2410_EXTINT_FALLEDGE;
        break;
 
    case IRQT_BOTHEDGE:  //双边沿触发,设置EXTINT0相应位为0x06
        newvalue = S3C2410_EXTINT_BOTHEDGE;
        break;

    case IRQT_LOW:                   //低电平触发,设置EXTINT0相应位为0x00
        newvalue = S3C2410_EXTINT_LOWLEV;
        break;

    case IRQT_HIGH:                 //高电平触发,设置EXTINT0相应位为0x01
        newvalue = S3C2410_EXTINT_HILEV;
        break;
    default:          
    }

/*更新EXTINT0相应位*/
    value = __raw_readl(extint_reg);
    value = (value & ~(7 << extint_offset)) | (newvalue << extint_offset);  //相应位设置
    __raw_writel(value, extint_reg);    //向extint_reg写入value值
    return 0;
}

  通过上面分析,就是将action->flags带入到desc[16]->chip->set_type里面,根据不同的中断来设置寄存器模式

2.2、free_irq()

  卸载中断的函数是free_irq(),也位于kernel/irq/ manage .c,函数原型如下:

free_irq(unsigned int irq, void *dev_id);

参数说明:
  unsigned int irq:要卸载的中断号
  void *dev_id:这个是要卸载的中断action下的哪个服务函数,
1)free_irq()代码如下:

void free_irq(unsigned int irq, void *dev_id)
{
    struct irq_desc *desc;
    struct irqaction **p;
    unsigned long flags;
    irqreturn_t (*handler)(int, void *) = NULL;
 
    WARN_ON(in_interrupt());
    if (irq >= NR_IRQS)
        return;

    desc = irq_desc + irq;                //根据中断号,找到数组
    spin_lock_irqsave(&desc->lock, flags);
    p = &desc->action;          //p指向中断里的action链表

    for (;;) {
        struct irqaction *action = *p;

        if (action) {        //在action链表中找到与参数dev_id相等的中断服务函数
            struct irqaction **pp = p;
            p = &action->next;       
            if (action->dev_id != dev_id)    //直到找dev_id才执行下面,进行卸载
                continue;          
            *pp = action->next;      //指向下个action成员,将当前的action释放掉
            #ifdef CONFIG_IRQ_RELEASE_METHOD
            if (desc->chip->release)   //执行chip->release释放中断服务函数相关的东西
                desc->chip->release(irq, dev_id);
            #endif
            if (!desc->action) {   //判断当前action成员是否为空,表示没有中断服务函数
                desc->status |= IRQ_DISABLED;
            if (desc->chip->shutdown)       //执行chip->shutdown关闭中断
                desc->chip->shutdown(irq);
            else                          //执行chip-> disable禁止中断
                desc->chip->disable(irq);
        }

        spin_unlock_irqrestore(&desc->lock, flags);
        unregister_handler_proc(irq, action);
        synchronize_irq(irq);
        if (action->flags & IRQF_SHARED)
            handler = action->handler;
        kfree(action);
        return;
    }

    printk(KERN_ERR "Trying to free already-free IRQ %d\n", irq);//没有找到要卸载的action成员

    spin_unlock_irqrestore(&desc->lock, flags);
    return;

    }

#ifdef CONFIG_DEBUG_SHIRQ
    if (handler) {

        /*
        * It's a shared IRQ -- the driver ought to be prepared for it
        * to happen even now it's being freed, so let's make sure....
        * We do this after actually deregistering it, to make sure that
        * a 'real' IRQ doesn't run in parallel with our fake
        */
        handler(irq, dev_id);
    }
#endif
}

  从上面分析,free_irq()函数主要通过irq和dev_id来找要释放的中断action
  若释放的中断action不是共享的中断(为空),则执行:

*pp = action->next;      //指向下个action成员,将当前的action释放掉
desc->chip->release(irq, dev_id);    //执行chip->release释放中断服务函数相关的东西

desc->status |= IRQ_DISABLED;            //设置desc[irq]->status标志位
desc->chip->[shutdown(irq)/ desible(irq)];    //关闭/禁止中断

  若释放的中断action是共享的中断(还有其它中断服务函数)的话就只执行:

*pp = action->next;      //指向下个action成员,将当前的action释放掉
desc->chip->release(irq, dev_id);    //执行chip->release释放中断服务函数相关的东西

三、自己编写中断方式按键驱动程序

本节目标:
  编写双边沿中断的按键驱动

3.1、确定需要配置的寄存器

  看原理图和2440手册,确定按键0~3分别是GPF0,GPF2,GPG3,GPG11
  所以需要设置4个按键的EINT0, EINT2, EINT11, EINT19的模式为双边沿,且设置按键引脚为中断引脚。
  这里我们只需要使用request_irq函数就行了, 在request_irq函数里会初始chip->set_type(设置引脚和中断模式)

3.2、驱动程序编写

1)首先添加头文件

#include <linux/irq.h>         //要用到IRQ_EINT0和IRQT_RISING这些变量

2)在second_drv_open函数中,申请4个中断:

/* IRQ_EINT0:     中断号, 定义在 asm/arch/irqs.h,被linux/irq.h调用
buttons_irq :     中断服务函数,
IRQT_ BOTHEDGE:   双边沿中断, 定义在 asm/irq.h,被linux/irq.h调用
“S1”:             保存文件到/proc/interrupt/S1,
1:                dev_id,中断函数的参数, 被用来释放中断服务函数,中断时并会传入中断服务函数
*/
request_irq(IRQ_EINT0, buttons_irq,IRQT_BOTHEDGE, “S1”, 1);  
request_irq(IRQ_EINT2, buttons_irq,IRQT_ BOTHEDGE, “S2”, 1);
request_irq(IRQ_EINT11, buttons_irq,IRQT_ BOTHEDGE, “S3”, 1);
request_irq(IRQ_EINT19, buttons_irq,IRQT_ BOTHEDGE, “S4”, 1);

3)在file_oprations结构体中添加.release成员函数,用来释放中断

static struct file_operations second_drv_fops={
     .owner = THIS_MODULE,
     .open = second_drv_open,
     .read = second_drv_read,
     .release=second_drv_class,        //里面添加free_irq函数,来释放中断服务函数
};

  然后写.release成员函数,释放中断:

int  second_drv_class(struct inode *inode, struct file  *file)
{
    free_irq(IRQ_EINT0,1);
    free_irq(IRQ_EINT2,1);
    free_irq(IRQ_EINT11,1);
    free_irq(IRQ_EINT19,1);

    return 0;
}

4)写action->handler中断服务函数,在第2)小节里request_irq函数的中断服务函数是buttons_irq

static irqreturn_t  buttons_irq (int irq, void *dev_id)     //irq:中断号, void *:表示支持所有类型
{
    printk(“irq=%d\n”);
    return IRQ_HANDLED;
}

3.3、编译,加载,测试

1)make后,然在开发板里insmod,并挂载好了buttons设备节点,如下图:
1740228-20190925153938713-881261734.png
2)通过exec 5</dev/buttons 将/dev/buttons 设备节点挂载到-sh进程下描述符5:
  如下图,使用ps查看-sh进程为801,然后ls -l /proc/801/fd 找到描述符5指向/dev/buttons
1740228-20190925153944464-1659293579.png
  如下图,并申请中断,当有按键按下时,就进入中断服务函数buttons_irq()打印数据:
1740228-20190925153948420-492681214.png
3)通过exec 5<&-将描述符5卸载
  会进入.release成员second_drv_class()函数释放中断,
  然后cat /proc/interrupts会发现申请的中断已经注销掉了,在-sh进程fd文件里也没有文件描述符5

3.4、改进中断按键驱动程序

改进措施:
  使用等待队列,让read函数没有中断时,进入休眠状态,降低CPU.
  使用dev_id来获取不同按键的状态,是上升沿还是下降沿触发?
1)接下来要用到以下几个函数:

s3c2410_gpio_getpin(unsigned int pin);     //获取引脚高低电平
//pin: 引脚名称,例如:S3C2410_GPA0,定义在<asm/arch/regs-gpio.h>

队列3个函数(声明队列,唤醒队列,等待队列):

static DECLARE_WAIT_QUEUE_HEAD(qname);
//声明一个新的等待队列类型的中断;qname:就是中断名字,被用来后面的唤醒中断和等待中断
wake_up_interruptible(*qname);
//唤醒一个中断,会将这个中断重新添加到runqueue队列(将中断置为TASK_RUNNING状态)qname:指向声明的等待队列类型中断名字
wait_event_interruptible(qname, condition);
//等待事件中断函数,用来将中断放回等待队列
//qname: (wait queue):为声明的等待队列的中断名字
//condition:状态,等于0时就是中断进入休眠, 1:退出休眠

  使用此函数的前提是condition要为0,然后将这个中断从runqueue队列中删除(将中断置为TASK_INTERRUPTIBLE状态),然后会在函数里一直for(; ;)判断condition为真才退出
  注意:此时的中断属于僵尸进程(既不在等待队列,也不在运行队列),当需要这个进程时,需要使用wake_up_interruptible(*qname)来唤醒中断
2)驱动程序步骤
2.1)定义引脚描述结构体数组,每个结构体都保存按键引脚和初始状态,然后在中断服务函数中通过s3c2410_gpio_getpin()来获取按键是松开还是按下(因为中断是双边沿触发),并保存在key_val里(它会在.read函数发送给用户层)

/*
*引脚描述结构体
*/
struct pin_desc{
    unsigned int  pin;
    unsigned int  pin_status;
};
/*
*key初始状态(没有按下): 0x01,0x02,0x03,0x04
*key状态(按下):                       0x81,0x82,0x83,0x84
*/
struct pin_desc  pins_desc[4]={
    {S3C2410_GPF0,0x01 }, 
    {S3C2410_GPF2, 0x02 },
    {S3C2410_GPG3, 0x03 },
    {S3C2410_GPG11,0x04},} ;

2.2)声明等待队列类型的中断button_wait:

static DECLARE_WAIT_QUEUE_HEAD(button_ wait);        //声明等待队列类型的中断

2.3)定义全局变量even _press,用于中断事件标志:

static volatile int even _press = 0;

2.4)在.read函数里,将even _press置0放入等待事件中断函数中,判断even _press为真,才发送数据:

    even_press = 0;                                
    wait_event_interruptible(button_ wait, even _press);   //当even _press为真,表示有按键按下,退出等待队列  
    copy_to_user(buf, &key_val, 1);       //even _press为真,有数据了,发送给用户层  

2.5)在中断服务函数里,发生中断时, 就将even _press置1,并唤醒中断button_wait(.read函数里就会发送数据给用户层):

even _press = 0;  
wake_up_interruptible(&button_wait);         //唤醒中断

3)更改测试程序second_interrupt_test.c

#include <sys/types.h>    //调用sys目录下types.h文件
#include <sys/stat.h>      //stat.h获取文件属性
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
/*secondtext            while一直获取按键信息   */
int main(int argc,char **argv)
{
    int fd,ret;
    unsigned int val=0;               
    fd=open("/dev/buttons",O_RDWR);       
    if(fd<0)
    {
        printf("can't open!!!\n");
        return -1;
    }

    while(1)
    {
        ret=read(fd,&val,1);    //读取一个值,(当在等待队列时,本进程就会进入休眠状态)
        if(ret<0)
        {
            printf("read err!\n");    
            continue;
        } 
        printf("key_val=0X%x\r\n",val);
    }
    return 0;
}

4)运行结果
  insmod second_interrupt.ko //挂载驱动设备
  ./second_interrupt_test & //后台运行测试程序
  创建了4个中断,如下图:
1740228-20190925153833388-994951160.png
  当没有按键按下时,这个进程就处于静止状态staitc,如下图所示:
1740228-20190925153839459-1816315362.png
  在等待队列(休眠状态)下,该进程占用了CPU0%资源,如下图所示:
1740228-20190925153845419-1034746085.png
  当有按键按下时,便打印数据,如下图所示:
1740228-20190925153849616-1056456447.png
本节驱动代码如下:

#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/irq.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <asm/uaccess.h>
#include <asm/io.h>

static struct class *seconddrv_class;                 
static struct class_device   *seconddrv_class_devs; 

/*    声明等待队列类型中断 button_wait      */
static DECLARE_WAIT_QUEUE_HEAD(button_wait);
 
/*
* 定义中断事件标志
* 0:进入等待队列        1:退出等待队列
*/
static int even_press=0;                          
/*
* 定义全局变量key_val,保存key状态
*/
static int key_val=0;                          

/*
*引脚描述结构体
*/
struct pin_desc{
    unsigned int  pin;
    unsigned int  pin_status;
};

/*
*key初始状态(没有按下): 0x01,0x02,0x03,0x04
*key状态(按下):   0x81,0x82,0x83,0x84
*/
struct pin_desc  pins_desc[4]={
    {S3C2410_GPF0,0x01 },
    {S3C2410_GPF2, 0x02 },
    {S3C2410_GPG3, 0x03 },
    {S3C2410_GPG11,0x04},} ;

int  second_drv_class(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 irqreturn_t  buttons_irq (int irq, void *dev_id)       //中断服务函数
{
    struct pin_desc *pindesc=(struct pin_desc *)dev_id;     //获取引脚描述结构体
    unsigned int  pin_val=0;                                       
    pin_val=s3c2410_gpio_getpin(pindesc->pin);

    if(pin_val)
    {
        /*没有按下 (下降沿),清除0x80*/                 
        key_val=pindesc->pin_status&0xef;
    }
    else
    {
        /*按下(上升沿),加上0x80*/
        key_val=pindesc->pin_status|0x80;
    }             

    even_press=1;               //退出等待队列
    wake_up_interruptible(&button_wait);      //唤醒 中断
                          
    return IRQ_HANDLED;
}

static int second_drv_open(struct inode *inode, struct file  *file)
{
    request_irq(IRQ_EINT0,buttons_irq,IRQT_BOTHEDGE,"S1",&pins_desc[0]);
    request_irq(IRQ_EINT2, buttons_irq,IRQT_BOTHEDGE, "S2", &pins_desc[1]);
    request_irq(IRQ_EINT11, buttons_irq,IRQT_BOTHEDGE, "S3", &pins_desc[2]);
    request_irq(IRQ_EINT19, buttons_irq,IRQT_BOTHEDGE, "S4", &pins_desc[3]);

    return 0;
}

 
static int second_drv_read(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{/*将中断 进入等待队列(休眠状态)*/
    wait_event_interruptible(button_wait, even_press);            

    /*有按键按下,退出等待队列,上传key_val 给用户层*/
    if(copy_to_user(buf,&key_val,sizeof(key_val)))
        return EFAULT;

    even_press=0; //数据发完后,立马设为休眠状态,避免误操作
    return 0;
}
 

static struct file_operations second_drv_fops={
    .owner = THIS_MODULE,
    .open = second_drv_open,
    .read = second_drv_read,
    .release=second_drv_class,    //里面添加free_irq函数,来释放中断服务函数
};

volatile int second_major;
static int second_drv_init(void)
{
    second_major=register_chrdev(0,"second_drv",&second_drv_fops);  //创建驱动
    seconddrv_class=class_create(THIS_MODULE,"second_dev");    //创建类名
    seconddrv_class_devs=class_device_create(seconddrv_class, NULL, MKDEV(second_major,0), NULL,"buttons");
    return 0;
}

static int second_drv_exit(void)
{
    unregister_chrdev(second_major,"second_drv");            //卸载驱动
    class_device_unregister(seconddrv_class_devs);          //卸载类设备        
    class_destroy(seconddrv_class);                      //卸载类
    return 0;
}

module_init(second_drv_init);
module_exit(second_drv_exit);
MODULE_LICENSE("GPL v2");

转载于:https://www.cnblogs.com/princepeng/p/11582953.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值