S3C2440之字符设备驱动详解

目录

一、概念介绍

二、LED驱动程序的测试

2.1 框架的搭建过程

2.2 测试改进

2.3 操作LED

三、按键驱动之查询方式

四、按键驱动之中断处理

4.1 Linux异常处理结构

4.2 Linux中断处理结构

4.3 编写代码

五、添加poll机制

六、异步通知

七、同步互斥与阻塞

7.1 变量实现同步出现的问题

7.2 方式一:原子操作

7.3 方式二:信号量

7.4 处理应用程序的阻塞标志

八、按键驱动利用定时器防抖动


一、概念介绍

  • C库实现open,read,write 调用之后会进到内核
  • open,read,write的实现实际上是实现了一条swi val指令,这条指令会产生一个异常,相当于发生中断一样,然后进到内核的异常处理函数里面
  • 内核系统调用接口 System Call interaface  根据发生异常的原因,调用不同处理函数sys_open、sys_read、sys_write
  • sys_open、sys_read、sys_write被VFS:virtual File System 处理打开不同文件来找到不同的驱动程序
  • 应用:open、read、write 对应驱动:led_open、led_read、led_write(自己命名并注册),而应用程序怎么去调用驱动里面的open、read、write依赖于驱动程序框架

二、LED驱动程序的测试

2.1 框架的搭建过程

  • 写出驱动程序中的led_open、led_read、led_write等等
  • 告诉内核

a.定义一个file_operations结构例如first_drv_fops

b.把这个结构告诉内核

register_chrdev(major, "first_drv", &first_drv_fops);其中major为主设备号,名字不重要自己命名,第三者参数为定义的file_operations结构

一般major写为0,使系统自动分配,其返回值就是major

c.驱动的入口函数来调用(可以自己命名例如,first_drv_init)

修饰入口函数module_init(first_drv_init);

d.有入口函数也就有出口函数(可以自己命名例如,firest_drv_exit)

修饰出口函数module_exit(firest_drv_exit);

  • 对于修饰函数module_init(),定义一个结构体,结构体里面有一个函数指针,指向入口函数,当我们去加载或者安装一个驱动的时候,内核会找到这个结构体,然后调用里面的函数指针,入口函数就会把file_operations结构告诉内核
  • 应用程序打开一个设备文件 /dev/xxx,通过命令ls-l,可以看到c_ _ _,_ _ _,_ _ _  major mior,其中c代表字符设备,若是d代表目录,major为主设备号,mior为次设备号
  • 怎么打开,根据设备类型c和主设备号major就能够找到注册进去的file_operations结构
  • register_chrdev最直接的作用:把file_operations结构填充进去
  • 小总结:应用程序app利用C库打开的设备文件例如为字符设备,由VFS在字符设备中根据主设备号找到chrdev数组对应的file_operations结构,然后驱动文件实现open、read、write,定义一个file_operations结构,用入口函数把结构体放到内核的数组里面去
  • DEMO:

驱动程序:

#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>


static int first_drv_open(struct inode *inode, struct file *file)
{
	printk("first_drv_open\n");
	return 0;
}


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

int major;

int firest_drv_init(void) //根据驱动名各有不同
{
	major = register_chrdev(0, "first_drv", &first_drv_fops); 
	firstdrv_class = class_create(THIS_MODULE, "firstdrv");
	
	return 0;
}

void firest_drv_exit(void) 
{
	unregister_chrdev(major, "first_drv");  //卸载驱动
}

module_init(firest_drv_init);
module_exit(firest_drv_exit);

 测试程序(应用程序):

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>

int main(int argc, char **argv)
{
	int fd;
	fd = open("dev/xyz",O_RDWR);
	if(fd < 0)
		printf("cant't open!\n");
	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	+= First_drv.o
  • 由于没有自动的创建设备节点,因此我们需要手动创建设备节点

mknod /dev/xyz c 111 0

  • 列出内核目前支持的设备,可以看出我们注册的信息

cat /proc/devices

  • 加载驱动,卸载驱动,查看驱动

insmod First_drv.ko

rmmod First_drv

lsmod

  • 编译驱动和测试程序到开发板上后,加载驱动,执行测试程序./firstdrvtest,结果如下,打开成功

first_drv_open

2.2 测试改进

  • 驱动设备号是我们手工建立的mknod /dev/xyz c 111 0,还有另外一种方法自动创建,udev机制实现,在S3C2440的busybox中mdev机制为udev的简化版本,根据系统信息,创建设备节点,在入口函数使用如下两个函数

class_create(THIS_MODULE, "firstdrv");

class_device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xyz");

  • 对于class_create函数创建了firstdrv这个类,对于class_device_create会根据这个类创建xyz这个设备,由mdev会自动创建/dev/xyz设备节点
  • 在出口函数消除

class_device_unregister(firstdrv_class_dev);

class_destroy(firstdrv_class);

  • 在修饰函数后面加上以下宏定义解决上面函数未识别的问题

MODULE_LICENSE("GPL");

  • 卸载之前的驱动,加载改进的驱动,使用ls -l /dev/xyz可看到自动创建的设备节点
  • 对于S3C2440,为什么会这样自动运行更改?因为脚本文件中/etc/init.d/rcS中echo /sbin/mdev > /proc/sys/kernel/hotplug,hotplug热拔插,当插入U盘等设备内核就会调用/proc/sys/kernel/hotplug,hotplug指向/sbin/mdev会自动去创建设备节点

2.3 操作LED

  • 写一个点LED驱动,需要框架(上述已构建)和完善硬件的操作

a.看原理图 b.看芯片手册 c.写代码

  • 对于单片机操作物理地址,对于linux在驱动程序操作虚拟地址
  • 用ioremap()把物理地址映射为虚拟地址,入口函数执行,以防多次调用
  • 在出口函数取消映射iounmap()
  • DEMO:

驱动程序:

#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>


static struct class *firstdrv_class;
static struct class_device	*firstdrv_class_dev;

volatile unsigned long *gpfcon = NULL;
volatile unsigned long *gpfdat = NULL;



static int first_drv_open(struct inode *inode, struct file *file)
{
	printk("first_drv_open\n");
	/* 配置 GPF456为输出 */
	*gpfcon &= ~((0x3<<(4*2)) | (0x3<<(5*2)) | (0x3<<(6*2)));
	*gpfcon |= ((0x1<<(4*2)) | (0x1<<(5*2)) | (0x1<<(6*2)));
	
	return 0;
}

static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
	int val;
	copy_from_user(&val, buf, count); //利用参数buf向用户提取数据,而copy_to_user()函数向用户传输数据

	if(val == 1){
		//点灯
		*gpfdat &= ~((1 << 4) |(1 << 5) | (1 << 6));		
	}else{
		//关灯
		*gpfdat |= (1 << 4) |(1 << 5) | (1 << 6);
	}

	
	printk("first_drv_write\n");
	return 0;
}

static struct file_operations first_drv_fops = {
    .owner  =   THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
    .open   =   first_drv_open,       
	.write	=	first_drv_write,	   
};

int major;

int firest_drv_init(void) //根据驱动名各有不同
{
	major = register_chrdev(0, "first_drv", &first_drv_fops); 

	firstdrv_class = class_create(THIS_MODULE, "firstdrv");
	
	firstdrv_class_dev = class_device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xyz"); /* /dev/leds */

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

void firest_drv_exit(void) 
{
	unregister_chrdev(major, "first_drv");  //卸载驱动

	class_device_unregister(firstdrv_class_dev);
	class_destroy(firstdrv_class);
	iounmap(gpfcon);	
}

module_init(firest_drv_init);
module_exit(firest_drv_exit);
MODULE_LICENSE("GPL");

测试程序:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>


/* firstdrvtest on 
 * firstdrvtest off
 */
int main(int argc, char **argv)
{
	int fd;
	int val = 1;
	fd = open("dev/xyz",O_RDWR);
	if(fd < 0)
		printf("cant't open!\n");
	if(argc != 2){
		printf("Usage:\n");
		printf("%s <on|off>\n", argv[0]);
	}
	if(strcmp(argv[1], "on") == 0){
		val = 1;
	}else{
		val = 0;
	}
	
	write(fd, &val, 4);
	return 0;
}
  •  根据自己的开发板来设置相应的寄存器,编译后加载驱动,执行测试程序./firstdrvtest on可以看到开发板上led全点亮

三、按键驱动之查询方式

  • 四个按键分别为GPF0、GPF2、GPG3和GPG11
  • 驱动程序中read成员使用copy_to_user函数将健值返回给应用程序

驱动程序:

#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>


static struct class *seconddrv_class;
static struct class_device	*seconddrv_class_dev;

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

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

static int second_drv_open(struct inode *inode, struct file *file)
{
	/* 配置GPF0 2  为输入引脚 */
	*gpfcon &= ~((0x3 << (0*2)) | (0x3 << (2*2))); 
	/* 配置GPG3 11 为输入引脚 */
	*gpgcon &= ~((0x3 << (3*2)) | (0x3 << (11*2)));

	return 0;
}

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

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

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

int major;
static int second_drv_init(void)
{
	major = register_chrdev(0, "second_drv", &second_drv_fops);

	seconddrv_class = class_create(THIS_MODULE, "seconddrv");
	
	seconddrv_class_dev = class_device_create(seconddrv_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 second_drv_exit(void)
{
	unregister_chrdev(major, "second_drv");
	class_device_unregister(seconddrv_class_dev);
	class_destroy(seconddrv_class);
	iounmap(gpfcon);
	iounmap(gpgcon);
}


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




测试程序:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>

/* seconddrvtest on 
 * seconddrvtest off
 */
int main(int argc, char **argv)
{
	int fd;
	unsigned char key_vals[4];
	int cnt =0;
	fd = open("dev/buttons", O_RDWR);
	if(fd < 0)
		printf("cant't open!\n");

	while(1){
		read(fd, key_vals, sizeof(key_vals));
		if(!key_vals[0] || !key_vals[1] || !key_vals[2] || !key_vals[3]){
			printf("%04d key pressed: %d %d %d %d\n", cnt++, key_vals[0], key_vals[1], key_vals[2], key_vals[3]);
		}
	}
	return 0;
}

  • 编译后加载驱动,执行测试程序,后台运行测试程序再用top命令查看,类似于windows的任务管理器,可见得测试程序占用98%CPU,因此这种while不断查询的方式耗资源

./seconddrvtest &

top

四、按键驱动之中断处理

4.1 Linux异常处理结构

异常,就是可以打断CPU正常运行流程的一些事情,比如外部中断、未定义的指令、试图修改只读的数据、执行swi指令(Software Interrupt Instruction,软件中断指令)等。当这些事情发生时,CPU暂停当前的程序,先处理异常事件,然后再继续执行被中断的程序。操作系统中经常通过异常来完成一些特定的功能

  • linux内核对异常的设置

内核在start_kernel函数(源码在: init/main.c中)中调用trap_init、init_lRQ两个函数来设置异常的处理函数。

trap_init函数分析:
trap  init函数(代码在 arch/arm/kernel/traps.c 中)被用来设置各种异常的处理向量,包括中断向量。所谓“向量”,就是一些被安放在固定位置的代码,当发生异常时,CPU 会自动执行这些固定位.置上的指令。ARM架构CPU的异常向量基址可以是Ox00000000,也可以是OxffffO000,Linux内核使用后者。trap_init 函数将异常向量复制到Oxff0000处,部分代码如下:

void __init trap_init(void)
{
...
721	memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
722	memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
	memcpy((void *)vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz);
...
}

第721行中,vectors等于OxfII0000。地址._vectors_start~-__vectors_end之间的代码就是异常向量,在 arch/arm/kernelentry-armv.S 中定义,它们被复制到地址Oxfm0000处。
异常向量的代码很简单,它们只是一些跳转指令。发生异常时,CPU自动执行这些指令,跳转去执行更复杂的代码,比如保存被中断程序的执行环境,调用异常处理函数,恢复被中断程序的执行环境并重新运行。这些“更复杂的代码”在地址_stubs_start~_stubs_end之间,它们在 arch/arm/kernelentry-armv.S中定义。第722行将它们复制到地址Oxffffo000+0x200处。

,中断也是一种异常,对于init_IRQ函数(代码在.arch/arm/kernel/irq.c中)被用来初始化中断的处理框架,设置各种中断的默认处理函数。当发生中断时,中断总入口函数 asm do_lRQ就可以调用这些函数作进一步处理。

4.2 Linux中断处理结构

  • 内核里中断处理是一种框架 ,单片机下的中断处理a.分辨是哪个中断b.调用处理函数c.清中断,对于linux,三项都是在asm_do_IRQ实现的

(1)发生中断时,CPU执行异常向量vector__irq的代码。
(2)在vector_irq里面,最终会调用中断处理的总入口函数asm _do_IRQ。( 3 ) asm_do_IRQ根据中断号调用irq_desc 数组项中的handle_irq。
( 4) handle_ irq 会使用chip成员中的函数来设置硬件,比如清除中断、禁止中断、重新使能中断等。
(5 ) handle_irq 逐个调用用户在 action链表中注册的处理函数。
可见,中断体系结构的初始化就是构造这些数据结构,比如irq_desc 数组项中的handle_irq、chip等成员;用户注册中断时就是构造action链表;用户卸载中断时就是从action链表中去除不需要的项。

  • Linux中断处理体系结构图

  •  注册中断程序request_irq函数在open函数中注册其内容:

1.分配irqaction结构,里面成员指向其参数

2.执行setup_irq(irq, 分配的irqaction结构)

                a.在结构体rq_desc[irq]数组项里面的action链表里加入irqaction 
                b.desc->chip->settype() 设置引脚
                c.desc->chip->startup/enable 使能中断

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

  • 参数irq中断号,例如按键可以从原理图看是外部中断0 在irqs.h中定义了中断号,参数二为中断处理函数,参数三为中断处理方法,在manage.c中setup.irq的set_type 在irq.h中定义,这里双边沿触发中断,参数四为ID,在卸载时需要其参数
  • 卸载中断处理程序在close中卸载,作用:一是将irqaction 从链表移除,二是禁止中断

free_irq(unsigned int irq, void *dev_id);

4.3 编写代码

  • 如果没有按键动作发生,则使进程休眠使用wait_event_interruptible()函数;
  • 唤醒休眠的进程,wake_up_interruptible();
  • 中断服务函数:用结构体的方式处理健值

驱动程序:

#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;

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;

	/* 读取PIN值 */
	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_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);
	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, "thirddrv");
	
	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);
}


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>
#include <unistd.h>

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

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

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

	return 0;
}
  • 用exec 5</dev/buttons打开驱动程序,利用cat /proc/interrupts可以看到哪些中断被打开了,ps当前sh进程为770,ls -l /proc/770/fd ,其中5就指向了/dev/buttons,关闭:exec 5<&-
  • 再次查看ls -l /proc/771/fd //5就没有了,cat /proc/interrupts也看不到中断,在close中释放中断

# insmod third_drv.ko
# exec 5</dev/buttons
# ps
  PID  Uid        VSZ Stat Command
    1 0          3092 S   init
    2 0               SW< [kthreadd]
    3 0               SWN [ksoftirqd/0]
    4 0               SW< [watchdog/0]
    5 0               SW< [events/0]
    6 0               SW< [khelper]
   55 0               SW< [kblockd/0]
   56 0               SW< [ksuspend_usbd]
   59 0               SW< [khubd]
   61 0               SW< [kseriod]
   73 0               SW  [pdflush]
   74 0               SW  [pdflush]
   75 0               SW< [kswapd0]
   76 0               SW< [aio/0]
  710 0               SW< [mtdblockd]
  745 0               SW< [kmmcd]
  762 0               SW< [rpciod/0]
  770 0          3096 S   -sh
  774 0          1308 R   ./seconddrvtest
  783 0          3096 R   ps

# ls -l /proc/770/fd
lrwx------    1 0        0              64 Jan  1 00:47 0 -> /dev/console
lrwx------    1 0        0              64 Jan  1 00:47 1 -> /dev/console
lrwx------    1 0        0              64 Jan  1 00:47 10 -> /dev/tty
lrwx------    1 0        0              64 Jan  1 00:47 2 -> /dev/console
lr-x------    1 0        0              64 Jan  1 00:47 5 -> /dev/buttons

  • 执行后台运行 ./thirddrvtest &,此时现象为等待按键按下直到休眠被中断打破
  • 执行top命令,其CPU占比为5%,相比查询方式极大提高了效率

五、添加poll机制

详解:https://blog.csdn.net/FrankyzhangC/article/details/6692210

  • 目的:根据poll机制定义的时间来查询驱动程序,当没有按键按下,每经过设定的时间申报一下
  • 应用程序通过poll函数来调用驱动中的poll函数
  • 上述代码中在驱动程序file_operations结构中添加poll成员,并注释掉read函数的休眠
ssize_t forth_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);
	ev_press = 0;
	return 1;
}

unsigned int forth_drv_poll(struct file *file, poll_table *wait)
{
	unsigned mask = 0;
	poll_wait(file, &button_waitq, wait);//不会立即休眠

	if(ev_press){
		mask |= POLLIN | POLLRDNORM;
	}
	return mask;
}


static struct file_operations forth_drv_fops = {
    .owner   =  THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
    .open    =  forth_drv_open,       
	.read	 =	forth_drv_read,
	.release =  forth_drv_close, 
	.poll    =  forth_drv_poll
};

测试程序:

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

  • struct pollfd *fds可查询多个驱动程序,nfds_t nfds这里查询1个,int timeout以ms为单位,根据返回值来判断是否超时
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <poll.h>
/* 
 * forthdrvtest
 */
int main(int argc, char **argv)
{
	int fd;
	unsigned char key_val;
	int ret;
	struct pollfd fds[1];
	
	fd = open("dev/buttons", O_RDWR);
	if(fd < 0)
		printf("cant't open!\n");

	fds[0].fd     = fd;
	fds[0].events = POLLIN;
	while(1){
		ret = poll(fds, 1, 5000);
		if(ret == 0){
			printf("time out\n");
		}else{
			read(fd, &key_val, 1);
			printf("key_val = 0x%x\n", key_val);
		}
		
	}

	return 0;
}
  • 编译后加载驱动,执行测试函数,当没按键按下,每隔5s返回

六、异步通知

  • 按键驱动程序方法中->查询方式:耗资源、中断方式:read、poll机制:指定超时时间,共同的特点:应用程序主动去Read
  • 异步通知:驱动程序提醒应用程序,应用程序再读取按键
  • 利用进程间发信号:signal()函数
  • 目标:按下按键时,驱动程序通知应用程序

步骤:1.应用程序:注册新号处理函数 2.谁发:驱动 3.发给应用程序app->app要告诉驱动PID 4.怎么发,在驱动中kill_fasyn();

  • 定义在fasync_struct结构体,file_operations结构中添加fasync成员,在中断函数中调用kill_fasyn()函数

驱动程序:

struct fasync_struct *button_async;

static irqreturn_t buttons_irq(int irq, void *dev_id)
{
	struct pin_desc *pindesc = (struct pin_desc *)dev_id;
	unsigned int pinval;

	/* 读取PIN值 */
	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);   /* 唤醒休眠的进程 */	

	kill_fasync(&button_async, SIGIO, POLL_IN);
	
	return IRQ_HANDLED;
}

static int fifth_drv_fasync (int fd, struct file *filp, int on)
{
	printk("driver: fifth_drv_fasync\n");
	return fasync_helper (fd, filp, on, &button_async); //结构体被初始化 kill_fasyn()就可以用
}

static struct file_operations fifth_drv_fops = {
    .owner   =  THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
    .open    =  fifth_drv_open,       
	.read	 =	fifth_drv_read,
	.release =  fifth_drv_close, 
	.poll    =  fifth_drv_poll,
	.fasync  =	fifth_drv_fasync,

};

 测试程序:

  • 调用kill_fasyn()函数,需要应用程序调用fcntl函数来初始化fasync_struct结构体才可以调用

为了使设备支持异步通知机制,驱动程序中涉及以下3项工作:
1. 支持F_SETOWN命令,能在这个控制命令处理中设置filp->f_owner为对应进程ID。
   不过此项工作已由内核完成,设备驱动无须处理。
2. 支持F_SETFL命令的处理,每当FASYNC标志改变时,驱动程序中的fasync()函数将得以执行。
   驱动中应该实现fasync()函数。 
3. 在设备资源可获得时,调用kill_fasync()函数激发相应的信号

fcntl(fd, F_SETOWN, getpid());  // 告诉内核,发给谁 把pid进程号告诉驱动程序
Oflags = fcntl(fd, F_GETFL); 
fcntl(fd, F_SETFL, Oflags | FASYNC);  // 改变fasync标记,最终会调用到驱动的faync > fasync_helper:初始化/释放fasync_struct

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <poll.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>

/* 
 * fifthdrvtest
 */

int fd;

void my_signal_fun(int signum)
{
	unsigned char key_val;
	read(fd, &key_val, 1);
	printf("key_val: 0x%x\n", key_val);
}

int main(int argc, char **argv)
{
	int ret;
	int Oflags;

	signal(SIGIO, my_signal_fun);

	fd = open("dev/buttons", O_RDWR);

	fcntl(fd, F_SETOWN, getpid());	
	
	Oflags = fcntl(fd, F_GETFL);  
	
	fcntl(fd, F_SETFL, Oflags | FASYNC); 
	
	if(fd < 0)
		printf("can't open!\n");

	while(1){
		sleep(1000);
		
	}

	return 0;
}
  • 验证结构体被初始化在驱动程序fifth_drv_fasync函数中打印验证
  • 编译加载驱动,执行测试程序,驱动中fasync成员被调用,结构体fasync_struct初始化成功,直到按键按下时,才将信息告诉应用程序

七、同步互斥与阻塞

7.1 变量实现同步出现的问题

  • 目的:同一时刻只能有一个应用程序打开/dev/buttons

修改程序如下

static int canopen = 1;
static int sixth_drv_open(struct inode *inode, struct file *file)
{
	if(--canopen != 0){
		canopen++;
		return -EBUSY;
	}	...
	
int sixth_drv_close(struct inode *inode, struct file *file)
{
	canopen++;
	...
  • 在汇编情况下,对于--canopen,分三步a.读出值 b.修改值 c.写回
  • 以上这种情况:由于linux是多任务的,假设有A程序打开,在汇编情况下,先读出canopen的值,若这时被切换到B程序打开,也会先读出canopen的值,再修改,此时B可以打开驱动,到切换到A程序时,由于一开始读出也是1,因此也能打开成功,这种概率很小,但不能忽视

7.2 方式一:原子操作

常用原子操作函数举例:
atomic_t v = ATOMIC_INIT(0);     //定义原子变量v并初始化为0
atomic_read(atomic_t *v);        //返回原子变量的值
void atomic_inc(atomic_t *v);    //原子变量增加1
void atomic_dec(atomic_t *v);    //原子变量减少1
int atomic_dec_and_test(atomic_t *v); //自减操作后测试其是否为0,为0则返回true,否则返回false

  • 对于原子操作atomic_dec_and_test读出修改写回一次性完成,不能被打断,将上述代码修改如下:
static atomic_t canopen = ATOMIC_INIT(1);  定义原子变量canopen并初始化为1

static int sixth_drv_open(struct inode *inode, struct file *file)
{

	if(!atomic_dec_and_test(&canopen)){  
		atomic_inc(&canopen);    //原子变量增加1
		return -EBUSY;
	}
    ...

int sixth_drv_close(struct inode *inode, struct file *file)
{
	atomic_inc(&canopen);
    ...

测试程序:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <poll.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>

/* 
 * sixthdrvtest
 */

int fd;

int main(int argc, char **argv)
{
	unsigned char key_val;
	int ret;
	int Oflags;
	
	if(fd < 0){
		printf("can't open!\n");
		return -1;
	}

	while(1){
		ret = read(fd, &key_val, 1);
		printf("key_val: 0x%x, ret = %d\n", key_val, ret);
		sleep(5);	 // 单位s	
	}

	return 0;
}
  • 编译加载驱动,执行测试程序,第一个测试程序打开成功,第二个,第三个等打开都会打印出can't open!

7.3 方式二:信号量

信号量(semaphore)是用于保护临界区的一种常用方法,只有得到信号量的进程才能执行临界区代码。
当获取不到信号量时,进程进入休眠等待状态。

定义信号量
struct semaphore sem;
初始化信号量
void sema_init (struct semaphore *sem, int val);
void init_MUTEX(struct semaphore *sem);//初始化为0

static DECLARE_MUTEX(button_lock);     //定义互斥锁

获得信号量
void down(struct semaphore * sem);
int down_interruptible(struct semaphore * sem); //获取不到锁,就休眠,休眠状态可被打断,别人可以结束掉改进程
int down_trylock(struct semaphore * sem);//获取不到锁,就返回,判断返回值
释放信号量
void up(struct semaphore * sem);

  • DEMO:
#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 <linux/poll.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 *sixthdrv_class;
static struct class_device	*sixthdrv_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,sixth_drv_read将它清0 */
static volatile int ev_press = 0;
struct fasync_struct *button_async;


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 atomic_t canopen = ATOMIC_INIT(1);  定义原子变量canopen并初始化为1
static DECLARE_MUTEX(button_lock);     //定义互斥锁

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

	/* 读取PIN值 */
	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);   /* 唤醒休眠的进程 */	

	kill_fasync(&button_async, SIGIO, POLL_IN);
	
	return IRQ_HANDLED;
}

static int sixth_drv_open(struct inode *inode, struct file *file)
{
#if 0
	if(!atomic_dec_and_test(&canopen)){  //自减操作后测试其是否为0,为0则返回true,否则返回false,读出修改写回一次性完成,不能被打断
		atomic_inc(&canopen);    //原子变量增加1
		return -EBUSY;
	}
#endif

	down(&button_lock); 
	
	/* 配置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 sixth_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);
	ev_press = 0;
	return 1;
}

int sixth_drv_close(struct inode *inode, struct file *file)
{
//	atomic_inc(&canopen);
	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]);
	
	/* 释放信号量 */
	up(&button_lock);
	return 0;
}

unsigned int sixth_drv_poll(struct file *file, poll_table *wait)
{
	unsigned mask = 0;
	poll_wait(file, &button_waitq, wait);//不会立即休眠

	if(ev_press){
		mask |= POLLIN | POLLRDNORM;
	}
	return mask;
}

static int sixth_drv_fasync (int fd, struct file *filp, int on)
{
	printk("driver: sixth_drv_fasync\n");
	return fasync_helper (fd, filp, on, &button_async);
}

static struct file_operations sixth_drv_fops = {
    .owner   =  THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
    .open    =  sixth_drv_open,       
	.read	 =	sixth_drv_read,
	.release =  sixth_drv_close, 
	.poll    =  sixth_drv_poll,
	.fasync  =	sixth_drv_fasync,

};

int major;
static int sixth_drv_init(void)
{
	major = register_chrdev(0, "sixth_drv", &sixth_drv_fops);

	sixthdrv_class = class_create(THIS_MODULE, "sixthdrv");
	
	sixthdrv_class_dev = class_device_create(sixthdrv_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 sixth_drv_exit(void)
{
	unregister_chrdev(major, "sixth_drv");
	class_device_unregister(sixthdrv_class_dev);
	class_destroy(sixthdrv_class);
	iounmap(gpfcon);
	iounmap(gpgcon);
}


module_init(sixth_drv_init);
module_exit(sixth_drv_exit);
MODULE_LICENSE("GPL");

测试程序:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <poll.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>

/* 
 * sixthdrvtest
 */

int fd;

int main(int argc, char **argv)
{
	unsigned char key_val;
	int ret;
	int Oflags;

	fd = open("dev/buttons", O_RDWR | O_NONBLOCK);

	
	if(fd < 0){
		printf("can't open!\n");
		return -1;
	}

	while(1){
		ret = read(fd, &key_val, 1);
		printf("key_val: 0x%x, ret = %d\n", key_val, ret);
		sleep(5);	 // 单位s	
	}

	return 0;
}
  • 编译加载驱动,执行测试程序,第一个打开成功,第二测试程序由于获取不到信号量,处于不可中断的睡眠状态

7.4 处理应用程序的阻塞标志

  • fd = open("...", O_RDWR | O_NONBLOCK); 处理阻塞
  • 修改驱动程序:
static int sixth_drv_open(struct inode *inode, struct file *file)
{
#if 0
	if(!atomic_dec_and_test(&canopen)){  //自减操作后测试其是否为0,为0则返回true,否则返回false,读出修改写回一次性完成,不能被打断
		atomic_inc(&canopen);    //原子变量增加1
		return -EBUSY;
	}
#endif

	if(file->f_flags & O_NONBLOCK){
		if(down_trylock(&button_lock))
			return -EBUSY;
	}else{
		/* 获取信号量 */
		down(&button_lock);
	}
    ...

	
ssize_t sixth_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{

	if(size != 1)
		return -EINVAL;
	
	if(file->f_flags & O_NONBLOCK){
		if(!ev_press)
			return -EBUSY;
	}else{
		/* 如果没有按键动作发生,休眠 */
		wait_event_interruptible(button_waitq, ev_press);
	}
    ...
  • 测试程序默认是阻塞,加上非阻塞标志:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <poll.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>

/* 
 * sixthdrvtest
 */

int fd;

void my_signal_fun(int signum)
{
	unsigned char key_val;
	read(fd, &key_val, 1);
	printf("key_val: 0x%x\n", key_val);
}

int main(int argc, char **argv)
{
	unsigned char key_val;
	int ret;
	int Oflags;

	fd = open("dev/buttons", O_RDWR | O_NONBLOCK);

	if(fd < 0){
		printf("can't open!\n");
		return -1;
	}

	while(1){
		ret = read(fd, &key_val, 1);
		printf("key_val: 0x%x, ret = %d\n", key_val, ret);
		sleep(5);	 // 单位s	
	}

	return 0;
}
  • 编译加载驱动,执行测试程序,第一个测试程序打开成功,第二个测试程序由于非阻塞的情况下又获取不到信号量,立即在open函数返回

八、按键驱动利用定时器防抖动

  • 问题:在按键按下时会出现抖动,以致于出现多次中断
  • 解决方法:在驱动程序中加上定时器,当按键按下时产生中断,重载定时器超时时间,在抖动的过程中产生中断都会重载定时器超时时间,直到定时器超时才对健值进行处理
  • 入口函数初始化定时器init_timer()函数,参考内核例子,其参数为一个struct timer_lis结构体

init_timer(&buttons_timer);
//buttons_timer.data = (unsigned long)sCpnt; //处理函数传参
//buttons_timer.expires = jiffies + 100*Hz; /*10s */  在中断服务函数再设置
buttons_timer.function = buttons_timer_function;
//buttons_timer.expires  = 0;

  • DEMO:

驱动程序:

#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 <linux/poll.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 *sixthdrv_class;
static struct class_device	*sixthdrv_class_dev;

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

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

static struct timer_list buttons_timer; //需要定义的结构体

static DECLARE_WAIT_QUEUE_HEAD(button_waitq);

static volatile int ev_press = 0;
struct fasync_struct *button_async;

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

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

static struct pin_desc *irq_pd;
static DECLARE_MUTEX(button_lock);     //定义互斥锁

static irqreturn_t buttons_irq(int irq, void *dev_id)
{
	/* 10ms后启动定时器 */
	irq_pd = (struct pin_desc *)dev_id;
	mod_timer(&buttons_timer, jiffies + HZ/100)	;
	return IRQ_HANDLED;                        
}

static int sixth_drv_open(struct inode *inode, struct file *file)
{
	if(file->f_flags & O_NONBLOCK){
		if(down_trylock(&button_lock))
			return -EBUSY;
	}else{
		down(&button_lock);
	}
	
	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 sixth_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{

	if(size != 1)
		return -EINVAL;
	
	if(file->f_flags & O_NONBLOCK){
		if(!ev_press)
			return -EBUSY;
	}else{
		wait_event_interruptible(button_waitq, ev_press);
	}
	
	copy_to_user(buf, &key_val, 1);
	ev_press = 0;
	return 1;
}

int sixth_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]);

	up(&button_lock);
	return 0;
}

unsigned int sixth_drv_poll(struct file *file, poll_table *wait)
{
	unsigned mask = 0;
	poll_wait(file, &button_waitq, wait);

	if(ev_press){
		mask |= POLLIN | POLLRDNORM;
	}
	return mask;
}

static int sixth_drv_fasync (int fd, struct file *filp, int on)
{
	printk("driver: sixth_drv_fasync\n");
	return fasync_helper (fd, filp, on, &button_async);
}

static struct file_operations sixth_drv_fops = {
    .owner   =  THIS_MODULE, 
    .open    =  sixth_drv_open,       
	.read	 =	sixth_drv_read,
	.release =  sixth_drv_close, 
	.poll    =  sixth_drv_poll,
	.fasync  =	sixth_drv_fasync,

};

int major;

static void buttons_timer_function(unsigned long data)//定时器处理函数
{
	struct pin_desc *pindesc = irq_pd;
	unsigned int pinval;

	if(!pindesc)
		return;

	/* 读取PIN值 */
	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); 

	kill_fasync(&button_async, SIGIO, POLL_IN);	
}

static int sixth_drv_init(void)
{
	init_timer(&buttons_timer);
	buttons_timer.function = buttons_timer_function;
	add_timer(&buttons_timer); 

	major = register_chrdev(0, "sixth_drv", &sixth_drv_fops);

	sixthdrv_class = class_create(THIS_MODULE, "sixthdrv");
	
	sixthdrv_class_dev = class_device_create(sixthdrv_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 sixth_drv_exit(void)
{
	unregister_chrdev(major, "sixth_drv");
	class_device_unregister(sixthdrv_class_dev);
	class_destroy(sixthdrv_class);
	iounmap(gpfcon);
	iounmap(gpgcon);
}


module_init(sixth_drv_init);
module_exit(sixth_drv_exit);
MODULE_LICENSE("GPL");
  • 将中断函数中处理按键的程序转移到定时器处理函数,而中断函数处理重装载超时时间,转移的时候需要对中断函数中的dev_id参数进行全局处理,定时器函数处理需要其参数
  • 在中断函数中的一条语句,修改定时器的超时时间 jiffies是个全局变量,系统每隔10ms产生系统时钟中断 cat /proc/interrupts,jiffies会累加,buttons_timer中有个expires超时时间变量,可以设置为当前值+某个值,1s就是HZ(100),即1s内jiffies会加100 

mod_timer(&buttons_timer, jiffies + HZ/100)    ;

  • 入口函数中add_timer(&buttons_timer),把定时器告诉内核,当定时时间到,buttons_timer_function函数就被调用
  • 入口函数中定时器初始化,一开始超时时间为0,而jiffier会大于0,因此在按键没按下会触发定时函数,解决方法在函数中加上判断if(!pindesc) return;

static void buttons_timer_function(unsigned long data)
{
    struct pin_desc *pindesc = irq_pd;
    unsigned int pinval;

    if(!pindesc)
        return;

测试程序:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <poll.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>

int fd;

int main(int argc, char **argv)
{
	unsigned char key_val;
	int ret;
	int Oflags;

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

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

  • 编译加载驱动,执行测试程序,执行结果,按下按键不会误多次触发

 

 

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值