字符设备驱动程序之中断方式的按键驱动_编写代码(八)

参考:https://blog.csdn.net/fengyuwuzu0519/article/details/71046343


字符设备驱动程序之中断方式的按键驱动_编写代码


使用中断方式,那么肯定有一个中断的初始化注册,就是告诉内核,我按下按键的时候会触发一个中断,同时一定有一个中断处理函数来处理中断发生时应该做什么。

linux内核中 如何告诉内核我按下按键了给我触发中断并实现中断处理函数呢。


注册中断(open驱动程序时调用):int request_irq(unsigned int irq, irq_handler_t handler,unsigned long irqflags, const char *devname, void *dev_id)

request_irq()函数参数解析:

※※※重要!!void *:void即“无类型”,void *则为“无类型指针”,可以指向任何数据类型。所以后面的代码传进来结构体。

1、从原理图可知IRQ中断号irq:(IRQ_EINT0……)


2、向系统注册的中断处理函数,中断发生时,系统调用这个函数,dev_id参数被传递给它,中断处理函数handler的格式:

3、触发方式:irqflags:type(IRQT_BOTHEDGE双边沿触发:上升沿和下降沿都可以触发中断)。


4、devname:中断名称,可以使用cat /proc/interrupts 查看此名称

5、dev_id:用法很简单。在free_irq卸载时,通过irq与dev_id结合在一起,来确定卸载哪一个irqaction结构。



释放中断(卸载驱动程序时,解除按键中断)

void free_irq(unsigned int irq, void *dev_id)

参数:irq中断号。dev_id用法很简单,在free_irq卸载时,通过irq与dev_id结合在一起,来确定卸载哪一个irqaction结构。


驱动程序:third_drv.c

/*
	一、驱动框架:

	1.先定义file_operations结构体,其中有对设备的打开,读和写的操作函数。
	2.分别定义相关的操作函数
	3.定义好对设备的操作函数的结构体(file_operations)后,将其注册到内核的file_operations结构数组中。
	  此设置的主设备号为此结构在数组中的下标。
	4.定义出口函数:卸载注册到内核中的设备相关资源
	5.修饰 入口 和 出口函数
	6.给系统提供更多的内核消息,在sys目录下提供设备的相关信息。应用程序udev可以据此自动创建设备节点,
	  创建一个class设备类,在此类下创建设备
*/

#include <linux/module.h>	//内涵头文件,含有一些内核常用函数的原形定义。
#include <linux/kernel.h>	//最基本的文件,支持动态添加和卸载模块。Hello World驱动要这一个文件就可以。
#include <linux/fs.h>		//包含了文件操作相关的struct的定义,例如struct file_operations
#include <linux/init.h>		
#include <linux/delay.h>
#include <linux/irq.h>
#include <asm/uaccess.h>	//包含了copy_to_user、copy_from_user等内核访问用户进程内存地址的函数定义
#include <asm/irq.h>
#include <asm/io.h>			//包含了ioremap、ioread等内核访问IO内存等函数的定义
#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;


static irqreturn_t button_irq(int irq, void *dev_id)
{
	printk("irq = %d\n",irq);
	return IRQ_HANDLED;
}


static int third_drv_open(struct inode *inode, struct file *file)
{
	/* 配置GPF0,2为输入引脚 */
	/* 配置GPF3,11为输入引脚 */
	request_irq(IRQ_EINT0,  button_irq, IRQT_BOTHEDGE, "S2", 1);	//配置为中断引脚
	request_irq(IRQ_EINT2,  button_irq, IRQT_BOTHEDGE, "S3", 1);
	request_irq(IRQ_EINT11, button_irq, IRQT_BOTHEDGE, "S4", 1);
	request_irq(IRQ_EINT19, button_irq, IRQT_BOTHEDGE, "S5", 1);
	
	return 0;
}

ssize_t third_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
	/* 返回4个引脚的电平 */
	unsigned char key_vals[4];
	int regval;

	//如果传进来的size不等于我们返回的4个字节,返回一个错误值
	if	(size != sizeof(key_vals))
		return -EINVAL;
	
	/* 读GPF0,2为输入引脚 */
	regval = *gpfdat;
	key_vals[0]=(regval & (1<<0)) ? 1 : 0;
	key_vals[1]=(regval & (1<<2)) ? 1 : 0;

	/* 读GPG3,11为输入引脚 */
	regval = *gpgdat;
	key_vals[2]=(regval & (1<<3)) ? 1 : 0;
	key_vals[3]=(regval & (1<<11)) ? 1 : 0;

	copy_to_user(buf, key_vals, sizeof(key_vals));
	
	return sizeof(key_vals);
}

int third_drv_close(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;
}

static struct file_operations third_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", &third_drv_fops);

	//创建一个类
	thirddrv_class = class_create(THIS_MODULE, "firstdrv");

	//在这个类下面再创建一个设备
	//mdev是udev的一个简化版本
	//mdev应用程序,就会被内核调用,会根据类和类下面的设备这些信息
	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; //加1,实际加4个字节

	gpgcon = (volatile unsigned long *)ioremap(0x56000060, 16);	//指向的是虚拟地址,第一个参数是物理开始地址,第二个是长度(字节)
	gpgdat = gpgcon + 1; //加1,实际加4个字节

	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");

验证驱动中断:(下面步骤,不写应用程序来验证驱动程序

使用命令:exec 5</dev/buttons,打开/dev/buttons这个设备,定位到文件描述符fd5,挂载到5下,会调用open

cat /proc/interrupts (产生的中断的信息)

使用ps命令查看,当前进程是-sh(shell),PID是772。

使用命令,ls -l /proc/772/fd,文件描述符fd5指向/dev/buttons,以后通过文件描述符5来访问buttons设备。

exec是用来执行一个进程的

linux中,所有设备都是文件,/dev/buttons也是文件,有文件描述符exec 5</dev/button  将  /dev/button文件关联到文件描述符5,以后对5的操作,就是对设备文件的操作) 
在Shell里执行exec 5</dev/button,因此5是Shell新打开的文件描述符,Shell的进程ID是772
在proc文件系统里772的fd目录,表示772进程打开的文件描述符) 

ps查看当前进程(可以查看进程状态s:休眠)

使用命令,exec 5<&-,关闭文件描述符fd5,释放中断。(会调用release


测试:(按下按键)

IRQ_EINT0:16=16+0,IRQ_EINT2:18=16+2,IRQ_EINT11:55=16+39,IRQ_EINT19(复位键):63=16+47。

因为是双边沿触发,所以每次按下按键,松开按键,打印两次。





优化上面的程序(读出按键值)

1、内核有一个系统函数s3c2410_gpio_getpin(引脚PIN):读出引脚的值。

2、定义了一个结构体pin_desc

     

    这个结构体在request_irq函数里传进去。

3、在read函数中,如果没有按键动作发生,休眠(让出CPU,不返回);如果有按键动作发生,直接返回。

     休眠:wait_event_interruptible(button_waitq, ev_press)

     (把进程挂在button_waitq队列里面)

     如果ev_press=0,让应用程序休眠,不返回,程序停止在此处。当被唤醒时,从此处继续执行。

     定义上面两个参数:

     static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
     /* 中断时间标志,中断服务程序将它置1,third_drv_read将它清0 */

     static volatile int ev_press=0;

4、当中断发生,执行中断处理函数,此时唤醒队列中次应用的进程,继续执行,返回结果。

      唤醒:(去button_waitq队列,把挂在这个队列的进程唤醒)

      ev_press = 1; /* 表示中断发生了 */

      wake_up_interruptible(&button_waitq); /* 唤醒休眠的进程,去button_wq队列的进程唤醒 */



完整的驱动代码:third_drv.c

/*
	一、驱动框架:

	1.先定义file_operations结构体,其中有对设备的打开,读和写的操作函数。
	2.分别定义相关的操作函数
	3.定义好对设备的操作函数的结构体(file_operations)后,将其注册到内核的file_operations结构数组中。
	  此设置的主设备号为此结构在数组中的下标。
	4.定义出口函数:卸载注册到内核中的设备相关资源
	5.修饰 入口 和 出口函数
	6.给系统提供更多的内核消息,在sys目录下提供设备的相关信息。应用程序udev可以据此自动创建设备节点,
	  创建一个class设备类,在此类下创建设备
*/

#include <linux/module.h>	//内涵头文件,含有一些内核常用函数的原形定义。
#include <linux/kernel.h>	//最基本的文件,支持动态添加和卸载模块。Hello World驱动要这一个文件就可以。
#include <linux/fs.h>		//包含了文件操作相关的struct的定义,例如struct file_operations
#include <linux/init.h>		
#include <linux/delay.h>
#include <linux/irq.h>
#include <asm/uaccess.h>	//包含了copy_to_user、copy_from_user等内核访问用户进程内存地址的函数定义
#include <asm/irq.h>
#include <asm/io.h>			//包含了ioremap、ioread等内核访问IO内存等函数的定义
#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;


/* 下面两个是定义休眠函数的参数 */
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 keyval;	//键值


/* 在request_irq函数中把结构体传进去 */
struct pin_desc pins_desc[4] = {	//键值先赋初始值0x01,0x02,0x03,0x04
	{S3C2410_GPF0,  0x01},	//pin=S3C2410_GPF0,  key_val(按键值)=0x01
	{S3C2410_GPF2,  0x02},	//pin=S3C2410_GPF2,  key_val(按键值)=0x02
	{S3C2410_GPG3,  0x03},	//pin=S3C2410_GPF3,  key_val(按键值)=0x03
	{S3C2410_GPG11, 0x04},	//pin=S3C2410_GPF11, key_val(按键值)=0x04
};


/*
 * 确定按键值
 */
static irqreturn_t button_irq(int irq, void *dev_id)	//中断处理函数
{
	/* irq = IRQ_EINT0 …… */
	/* dev_id = 结构体struct pins_desc */

	struct pin_desc * pindesc = (struct pin_desc *)dev_id;
	unsigned int pinval;

	/* 读取引脚PIN值 */
	pinval = s3c2410_gpio_getpin(pindesc->pin);

	/* 确定按键值,按下管脚低电平,松开管脚高电平 */
	if(pinval)
	{
		/* 松开 */				
		keyval = 0x80 | pindesc->key_val;	//规定的:0x8X
	}
	else
	{
		/* 按下 */
		keyval = pindesc->key_val;	//0x0X
	}

	/* 唤醒 */
	ev_press = 1;	/* 表示中断发生了 */
	wake_up_interruptible(&button_waitq);	/* 唤醒休眠的进程,去button_wq队列,把挂在队列下的进程唤醒 */	
	
	return IRQ_RETVAL(IRQ_HANDLED);
}


static int third_drv_open(struct inode *inode, struct file *file)
{
	/* 配置GPF0,2为输入引脚 */
	/* 配置GPF3,11为输入引脚 */

	/* request_irq函数的第五个参数是void *,为无类型指针,可以指向任何数据类型 */
	request_irq(IRQ_EINT0,  button_irq, IRQT_BOTHEDGE, "S2", &pins_desc[0]);
	request_irq(IRQ_EINT2,  button_irq, IRQT_BOTHEDGE, "S3", &pins_desc[1]);
	request_irq(IRQ_EINT11, button_irq, IRQT_BOTHEDGE, "S4", &pins_desc[2]);
	request_irq(IRQ_EINT19, button_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;

	/* 如果没有按键动作,休眠,休眠:让出CPU */
	/* 休眠时,把进程挂在button_wq        队列里 */
	/* 如果休眠后被唤醒,就会从这里继续往下执行 */
	/* 一开始没有按键按下,ev_press = 0 */
	wait_event_interruptible(button_waitq, ev_press);//ev_press=0,休眠,让我们的测试程序休眠;ev_press!=0,直接往下运行

	/* 如果有按键动作,返回键值 */
	copy_to_user(buf, &keyval, 1);	//把键值 拷回去
	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 third_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", &third_drv_fops);

	//创建一个类
	thirddrv_class = class_create(THIS_MODULE, "firstdrv");

	//在这个类下面再创建一个设备
	//mdev是udev的一个简化版本
	//mdev应用程序,就会被内核调用,会根据类和类下面的设备这些信息
	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; //加1,实际加4个字节

	gpgcon = (volatile unsigned long *)ioremap(0x56000060, 16);	//指向的是虚拟地址,第一个参数是物理开始地址,第二个是长度(字节)
	gpgdat = gpgcon + 1; //加1,实际加4个字节

	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");



测试程序:thirddrvtest.c

#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;
	int cnt = 0;
	
	fd = open("/dev/buttons", O_RDWR);
	if (fd < 0)
	{
		printf("can't open!\n");
	}

	while (1)
	{
		//用查询方式读按键坏处:占用CPU大
		//根本不知道按键什么时候按下,不可预料,只能不断地读,知道它返回
		read(fd, &key_val, 1);
		printf("key_val = 0x%x\n", key_val);
	}
	
	return 0;
}


Makefile文件

KERN_DIR = /work/system/linux-2.6.22.6

all:
	make -C $(KERN_DIR) M=`pwd` modules 

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order

obj-m	+= third_drv.o

最后进行测试:

insmod third_drv.ko

./thirddrvtest &  (在后台执行)

然后按下开发板的四个按键,再松开。。



怎么卸载模块呢?

因为模块正在被使用

所以,先找到在后台运行的程序thirddrvtest,查看它的进程号为845,

用命令kill -9 845杀死进程,

再卸载模块。






书上或者光盘的参考代码:s3c24xx_button.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/irq.h>
#include <linux/interrupt.h>
#include <asm/uaccess.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>

#define DEVICE_NAME     "buttons"   /* 加载模式后,执行”cat /proc/devices”命令看到的设备名称 */
#define BUTTON_MAJOR    232         /* 主设备号 */

struct button_irq_desc {
    int irq;
    unsigned long flags;
    char *name;
};

/* 用来指定按键所用的外部中断引脚及中断触发方式, 名字 */
static struct button_irq_desc button_irqs [] = {
    {IRQ_EINT19, IRQF_TRIGGER_FALLING, "KEY1"}, /* K1 */
    {IRQ_EINT11, IRQF_TRIGGER_FALLING, "KEY2"}, /* K2 */
    {IRQ_EINT2,  IRQF_TRIGGER_FALLING, "KEY3"}, /* K3 */
    {IRQ_EINT0,  IRQF_TRIGGER_FALLING, "KEY4"}, /* K4 */
};

/* 按键被按下的次数(准确地说,是发生中断的次数) */
static volatile int press_cnt [] = {0, 0, 0, 0};

/* 等待队列: 
 * 当没有按键被按下时,如果有进程调用s3c24xx_buttons_read函数,
 * 它将休眠
 */
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);

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


static irqreturn_t buttons_interrupt(int irq, void *dev_id)
{
    volatile int *press_cnt = (volatile int *)dev_id;
    
    *press_cnt = *press_cnt + 1; /* 按键计数加1 */
    ev_press = 1;                /* 表示中断发生了 */
    wake_up_interruptible(&button_waitq);   /* 唤醒休眠的进程 */
    
    return IRQ_RETVAL(IRQ_HANDLED);
}


/* 应用程序对设备文件/dev/buttons执行open(...)时,
 * 就会调用s3c24xx_buttons_open函数
 */
static int s3c24xx_buttons_open(struct inode *inode, struct file *file)
{
    int i;
    int err;
    
    for (i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++) {
        // 注册中断处理函数
        err = request_irq(button_irqs[i].irq, buttons_interrupt, button_irqs[i].flags, 
                          button_irqs[i].name, (void *)&press_cnt[i]);
        if (err)
            break;
    }

    if (err) {
        // 释放已经注册的中断
        i--;
        for (; i >= 0; i--)
            free_irq(button_irqs[i].irq, (void *)&press_cnt[i]);
        return -EBUSY;
    }
    
    return 0;
}


/* 应用程序对设备文件/dev/buttons执行close(...)时,
 * 就会调用s3c24xx_buttons_close函数
 */
static int s3c24xx_buttons_close(struct inode *inode, struct file *file)
{
    int i;
    
    for (i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++) {
        // 释放已经注册的中断
        free_irq(button_irqs[i].irq, (void *)&press_cnt[i]);
    }

    return 0;
}


/* 应用程序对设备文件/dev/buttons执行read(...)时,
 * 就会调用s3c24xx_buttons_read函数
 */
static int s3c24xx_buttons_read(struct file *filp, char __user *buff, 
                                         size_t count, loff_t *offp)
{
    unsigned long err;
    
    /* 如果ev_press等于0,休眠 */
    wait_event_interruptible(button_waitq, ev_press);

    /* 执行到这里时,ev_press等于1,将它清0 */
    ev_press = 0;

    /* 将按键状态复制给用户,并清0 */
    err = copy_to_user(buff, (const void *)press_cnt, min(sizeof(press_cnt), count));
    memset((void *)press_cnt, 0, sizeof(press_cnt));

    return err ? -EFAULT : 0;
}

/* 这个结构是字符设备驱动程序的核心
 * 当应用程序操作设备文件时所调用的open、read、write等函数,
 * 最终会调用这个结构中的对应函数
 */
static struct file_operations s3c24xx_buttons_fops = {
    .owner   =   THIS_MODULE,    /* 这是一个宏,指向编译模块时自动创建的__this_module变量 */
    .open    =   s3c24xx_buttons_open,
    .release =   s3c24xx_buttons_close, 
    .read    =   s3c24xx_buttons_read,
};

/*
 * 执行“insmod s3c24xx_buttons.ko”命令时就会调用这个函数
 */
static int __init s3c24xx_buttons_init(void)
{
    int ret;

    /* 注册字符设备驱动程序
     * 参数为主设备号、设备名字、file_operations结构;
     * 这样,主设备号就和具体的file_operations结构联系起来了,
     * 操作主设备为BUTTON_MAJOR的设备文件时,就会调用s3c24xx_buttons_fops中的相关成员函数
     * BUTTON_MAJOR可以设为0,表示由内核自动分配主设备号
     */
    ret = register_chrdev(BUTTON_MAJOR, DEVICE_NAME, &s3c24xx_buttons_fops);
    if (ret < 0) {
      printk(DEVICE_NAME " can't register major number\n");
      return ret;
    }
    
    printk(DEVICE_NAME " initialized\n");
    return 0;
}

/*
 * 执行”rmmod s3c24xx_buttons.ko”命令时就会调用这个函数 
 */
static void __exit s3c24xx_buttons_exit(void)
{
    /* 卸载驱动程序 */
    unregister_chrdev(BUTTON_MAJOR, DEVICE_NAME);
}

/* 这两行指定驱动程序的初始化函数和卸载函数 */
module_init(s3c24xx_buttons_init);
module_exit(s3c24xx_buttons_exit);

/* 描述驱动程序的一些信息,不是必须的 */
MODULE_AUTHOR("http://www.100ask.net");             // 驱动程序的作者
MODULE_DESCRIPTION("S3C2410/S3C2440 BUTTON Driver");   // 一些描述信息
MODULE_LICENSE("GPL");                              // 遵循的协议


猜你喜欢

转载自blog.csdn.net/xiaodingqq/article/details/80305626