Linux驱动开发——按键为例介绍Linux内核中断


所有的热爱都要不遗余力,真正喜欢它便给它更高的优先级,和更多的时间吧!

关于 LINUX驱动 的其它文章请点击这里:     LINUX驱动


1. 简介

     linux的中断处理过程和ARM裸板中的中断处理过程是一致的。不同点在于裸板开始时所有的软件都是自行编程完成的,在linux中很多中断相关的代码内核已经实现完毕了,某个中断产生后应该做什么样的具体工作没有完成,这就需要我们自己编程实现。

    ● 中断服务程序有如下特点

 (不属于)  1)中断处理程序不属于进程,它运行于中断上下文
 (不交换)  2)在中断上下文中不能做用户空间和内核空间的数据交互(linux不允许,STM32中裸板中可以)
 				 copy_to_user/copy_from_user /kmalloc  
 (不阻塞)  3)在中断上下文中不允许执行引起阻塞或者睡眠的函数,如:
             	 sleep //睡眠函数
             	 recv //阻塞函数
 (栈)  	  4)中断使用的栈为独立的栈空间,栈空间为一个4KB的内存页 
 (快)    5)要求对应的处理过程,执行速度越快越好

2. linux内核中中断的注册与注销

● 注册

/*
@function:注册中断服务程序 
@para:
    【irq】: 中断号,内核中将所有的中断源做了统一的编号, 获取linux内核中断号的两种方式:
         1)int gpio_to_irq(unsigned gpio)----将管脚编号gpio转换为对应的中断编号
         2) xxx_irq.h----IRQ_GPIO_A_START + index
	【handler】: 要注册的irq号中断源对应的中断服务程序(用户端)
         btn_isr 也被称作回调函数/钩子函数   (strcmp/open调用别人写的代码,而我们写的函数,被系统调用,就叫做钩子函数)
     【flags】:  设置哪种情况下触发irq号中断
        IRQF_TRIGGER_RISING, 上升沿触发中断
        IRQF_TRIGGER_FALLING
        IRQF_TRIGGER_HIGH,高电平触发中断
        IRQF_TRIGGER_LOW
        IRQF_SHARED: 共享中断
    【name】: 名称
    【dev】:  调用handler 函数时传递的参数(用户调用使用的)
@return:注册成功返回0  失败返回非0
*/
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)

● 注销

/*
@function:注销中断服务程序
@note:当调用request_irq(....,dev)  当调用free_iqr(.......,dev)         最后一参数要保持一致     不然注销失败 
*/
void free_irq(unsigned int irq, void *dev)

3. 代码

硬件上使用了四个按键,都是下降沿触发。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/gpio.h>
#include <linux/interrupt/h>

MODELE_LICENSE("GPL");

struct btn_desc
{
    
    
    int irq;  //中断号
    char *name;
} 

struct btn_desc btns[]=
{
    
    
    {
    
    IRQ_GPIO_A_START+28, "K1"},
    {
    
    IRQ_GPIO_B_START+30, "K2"},
    {
    
    IRQ_GPIO_B_START+31, "K3"}
    {
    
    IRQ_GPIO_B_START+9,  "K4"}, 
}

/*  回调/钩子函数, 用户自定义
typedef irqreturn_t (*irq_handler_t)(int, void *);
  第一个参数:中断号
  第二个参数:用户参数    
当按键触发的时候的时候,会打印 K1~K4         */
irqreturn_t btn_isr(int irq, void *dev)
{
    
    
    //int test = *((int *)dev);
    sturct btn_sesc *pdata = (sturct btn_desc *)dev;
    printk("%s press!\n",pdate->name);
    return IRQ_HANDLED;
}

//初始化:注册中断服务函数
int __init btn_drv_init(void) 
{
    
    
    //int irq = gqio_to_irq(PAD_GPIO_A + 28);           //获取终端号 
    for(i=0; i<ARRAY_SIZE(btns); i++))
    {
    
    
	    //注册中断服务函数  约定一下,按键的时候才使用,具体见上文的函数解释
        int ret = request_irq(btns[i].irq, btn_isr, IRQF_RTIGGER_FALLING, btns[i].name, (void *)&(btns[i]));  
        if(ret)
        {
    
    
            printk("request_irq failed!\n");
            return -EAGAIN;
        }
    }
    return 0;
}

//注销中断服务程序
void __exit btn_drv_exit(void)
{
    
    
	int i = 0;
    for(i=0; i<ARRAY_SIZE(btns); i++)
    {
    
    
        //int irq = gqio_to_irq(PAD_GPIO_A + 28); 
        free_irq(btns[i].irq, (void *)&(btns[i]));
    }
}
...
module_init(btn_drv_init);
module_exit(btn_drv_exit);

4. 测试

向内核加载模块:

insmod btn_drv.ko
insmod: can't insert 'btn_drv.ko': Resource temporarily unavailable
#原因: 内核中自带了按键的驱动程序,在其中通过request_irq的方式注册了K1对应的中断服务程序,导致再次调用request_irq时注册失败

解决方法:

make menuconfig
  Device Drivers  --->
      Input device support  --->
            [*]   Keyboards  --->  
                   < >   SLsiAP push Keypad support
  make uImage  #让开发板使用新内核
  cp arch/arm/boot/uImage /tftpboot/
  tftp 48000000 uImage
  mmc write 48000000 800 3000

重启后,再向内核加载模块:

  insmod btn_drv.ko
  # 按下K1观察实验效果

查看中断信息:

  cat /proc/interrupts
  #中断号   中断产生的次数
  134:          0           GPIO   K1

关于 LINUX驱动 的其它文章请点击这里:     LINUX驱动

猜你喜欢

转载自blog.csdn.net/qq_16504163/article/details/111154375