Linux字符设备驱动-KEY-阻塞IO、非阻塞IO、信号驱动IO

1.概述

实现了按键的字符驱动,支持在应用层使用open、read、poll、select及signal函数,支持阻塞和非阻塞IO,支持异步通知IO。可以用test.c进行测试,测试命令为./test -a b表示阻塞读,nb表示非阻塞读,poll表示使用poll函数,select表示使用select函数,signal表示使用信号。测试结果会输出按键按下和松开的次数。

2.中断

2.1.申请中断和释放中断

对于Linux内核来说,中断是一种资源,由内核统一管理。使用中断之前必须向内核申请,使用完毕后必须释放,把资源规划给内核。驱动使用request_irq申请中断,返回值为0表示申请成功,为负值时表示错误码,使用free_irq释放中断。devm_request_irq申请的资源不需要显示的释放。
irq为要申请的虚拟中断号,handler为中断处理函数(中断上半部分)指针,irqflags与中断管理相关的位掩码,可以指定中断触发方式及处理方式,devname传递给request_irq的字符串,用来在/proc/interrupts中显示中断的拥有者,dev_id用于共享的中断信号线,驱动程序可使用它来识别那个设备产生了中断,类似于struct file中的private_data指针。
由于设备上的中断资源有限,建议在设备打开时申请中断资源,设备关闭时释放中断资源,不建议在模块初始化的时候申请中断。

	include <linux/interrupt.h>
	// 注册中断上半部分处理函数
	// irq-软件(虚拟)中断号,不是硬件中断号。虚拟中断号是内核根据硬件中断号进行分配的,由内核管理,可通过
	//     irq_of_parse_and_map读取设备树中的硬件中断信息,然后返回虚拟中断号
	// handler-中断处理函数,执行时间尽可能短,不能睡眠
	// flags-中断标志
	// name-中断名称
	// dev-传递给中断处理函数handler的参数
	int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)
	// 释放注册的中断上半部分处理函数
	void free_irq(unsigned int irq, void *dev_id)
	// 注册中断上半部分处理函数,驱动卸载时可自动释放注册的中断上半部分处理函数
	// dev-设备驱动的设备结构体
	int devm_request_irq(struct device *dev, unsigned int irq, irq_handler_t handler, 
						unsigned long irqflags, const char *devname, void *dev_id)   
	// 将设备树中的硬件中断信息映射位虚拟中断号
	// dev-为设备树中的设备节点
	// index-为中断属性的索引号
	unsigned int irq_of_parse_and_map(struct device_node *dev, int index)
	#define IMX_GPIO_NR(bank, nr)	(((bank) - 1) * 32 + (nr))
	#include  
	// 获取gpio的虚拟中断号
	// gpio-根据引脚所在的gpio back和pin转换而来,不同的平台转转换方法不一样,imx的采用IMX_GPIO_NR宏进行转换。
	int gpio_to_irq(unsigned int gpio)

中断处理方式标志:

	#define IRQF_SHARED		    0x00000080  // 共享中断
	#define IRQF_PROBE_SHARED	0x00000100
	#define __IRQF_TIMER		0x00000200
	#define IRQF_PERCPU		    0x00000400
	#define IRQF_NOBALANCING	0x00000800  // 不受中断平衡影响
	#define IRQF_IRQPOLL		0x00001000
	#define IRQF_ONESHOT		0x00002000  // 直到中断处理线程开始执行时才取消屏蔽中断
	#define IRQF_NO_SUSPEND		0x00004000
	#define IRQF_FORCE_RESUME	0x00008000
	#define IRQF_NO_THREAD		0x00010000
	#define IRQF_EARLY_RESUME	0x00020000
	#define IRQF_COND_SUSPEND	0x00040000
	// 定时器中断
	#define IRQF_TIMER	(__IRQF_TIMER | IRQF_NO_SUSPEND | IRQF_NO_THREAD)

中断触发方式标志:

	#define IRQF_TRIGGER_NONE	0x00000000   // 不指定触发方式
	#define IRQF_TRIGGER_RISING	0x00000001   // 上升沿
	#define IRQF_TRIGGER_FALLING 0x00000002  // 下降沿
	#define IRQF_TRIGGER_HIGH	0x00000004   // 高电平
	#define IRQF_TRIGGER_LOW	0x00000008   // 低电平

中断处理函数的定义为typedef irqreturn_t (*irq_handler_t)(int, void *),第一个参数为中断号,第二个参数为调用request_irq申请中断时的dev。返回值有如下三种:

	include <linux/irqreturn.h>
	enum irqreturn {
		IRQ_NONE		= (0 << 0),    // 非本设备中断
		IRQ_HANDLED		= (1 << 0),    // 中断处理完毕
		IRQ_WAKE_THREAD	= (1 << 1),    // 唤醒中断处理线程,用于处理中断后半部分
	};

申请中断时如果需要为设备建立一个中断处理线程,需要使用request_threaded_irq或者devm_request_threaded_irq函数,handler为主处理函数,位于中断上半部分,thread_fn为中断处理线程函数,位于中断上半部分。如果中断处理函数返回IRQ_WAKE_THREAD,则会唤醒中断线程。假如handler为空,则调用默认的主处理函数irq_default_primary_handler,该函数返回IRQ_WAKE_THREAD

	include <linux/interrupt.h>
	int request_threaded_irq(unsigned int irq, irq_handler_t handler,  irq_handler_t thread_fn, unsigned long flags, const char *name, void *dev);
	int devm_request_threaded_irq(struct device *dev, unsigned int irq,irq_handler_t handler, irq_handler_t thread_fn,unsigned long irqflags, const char *devname,void *dev_id);

2.2.禁用中断

使用disable_irqdisable_irq_nosync禁用单个中断,前者在禁止中断之前如有中断发生则会等待中断完成,后者禁止中断并立即返回,使用enable_irq使能中断。disable_irqdisable_irq_nosync可嵌套使用,但必须和enable_irq配对。

	include <linux/interrupt.h>
	void disable_irq(unsigned int irq)
	void disable_irq_nosync(unsigned int irq)
	void enable_irq(unsigned int irq)

2.3.禁用本地CPU中断

	include <linux/irqflags.h>
	// 禁止当前处理器的所有中断,并保存标志
	#define local_irq_save(flags) do {raw_local_irq_save(flags);} while (0)
	// 使能当前处理器的所有中断,并恢复标志
	#define local_irq_restore(flags) do { raw_local_irq_restore(flags); } while (0)
	// 禁止当前处理器的所有中断
	#define local_irq_enable()	do { raw_local_irq_enable(); } while (0)
	// 使能当前处理器的所有中断
	#define local_irq_disable()	do { raw_local_irq_disable(); } while (0)

3.内核时钟中断

内核正常运行,需要系统时钟周期性的产生中断,产生中断的频率由宏定义HZ决定,大多数平台定义范围为50-1200,ARM平台常见定义为100.如想改变系统时钟中断发生的频率,可通过修改HZ值,但修改后要重新编译内核及所有模块。
每当时钟中断发生时,内核内部计数器加一,计数器在系统引导的时候初始化为0,因此它的值为自启动以来的时钟滴答数,此计数器为jiffies_64,为64位的变量。但在驱动程序中通常访问的是jiffies,如unsigned long为64位,那么jiffiesjiffies_64相同,如unsigned long为32位,那么jiffiesjiffies_64的低32位,通常情况下访问jiffies,速度会快一点。

	include <linux/jiffies.h>
	u64 __jiffy_data jiffies_64;
	unsigned long volatile __jiffy_data jiffies;
    // 时间和jiffies的转化关系可用下面的函数实现。
	// jiffies值转换为毫妙数
	unsigned int jiffies_to_msecs(const unsigned long j);
	// jiffies值转换为微妙数
	unsigned int jiffies_to_usecs(const unsigned long j);
	// 毫妙数转换为jiffies
	unsigned long msecs_to_jiffies(const unsigned int m);
	// 微妙数转换为jiffies
	unsigned long usecs_to_jiffies(const unsigned int u);

4.内核定时器

如果需要在将来的某个时间点调度执行某个动作,同时在该时间点到达之前不会阻塞当前进程,则可以使用内核定时器。内核定时器可用来在未来的某个特定时间点(基于时钟滴答)调度执行某个函数,从而完成特定的任务。定时器基于软中断实现,定时器到期执行的函数需要满足一下几点:
(1)不允许访问用户空间。因为没有进程上下文,无法将任何特定进程与用户空间关联起来
(2)current指针在原子模式下没有任何意义,也是不可用的,因为相关代码和被中断的进程没有任何关联。
(3)不能执行休眠或调度。原子代码不可以调用schedule或者wait_event,也不能调用任何可能引起休眠的函数,如调用kmalloc(..., GFP_KERNEL)、使用信号量等。
在SMP系统中,定时器函数会由注册它的CPU执行,不会调度到其他CPU上执行,这样可以尽可能获得缓存的局域性。内核为驱动程序提供了一组用来声明、注册和删除内核定时器的函数。

	include <linux/timer.h>
	struct timer_list {
		/* All fields that change during normal runtime grouped to the same cacheline */
		unsigned long expires;  // 定时器到期的jiffies值
		void (*function)(unsigned long);  // 定时器到期执行的函数
		unsigned long data;  // 定时器到期执行函数的参数
	};
	// 定义并初始化定时器
	#define DEFINE_TIMER(_name, _function, _expires, _data)	struct timer_list _name = TIMER_INITIALIZER(_function, _expires, _data)  
	// 初始化定时器
	#define init_timer(timer)  __init_timer((timer), 0)
	// 注册定时器
	void add_timer(struct timer_list *timer);
	// 删除定时器
	int del_timer(struct timer_list * timer);
	// 更新定时器的到时时间
	int mod_timer(struct timer_list *timer, unsigned long expires);

5.阻塞型I/O

5.1.休眠

当一个进程被置入休眠状态时,它会被标记为一种特殊状态并从调度器的运行队列中移走。直到某些情乱下修改了这个状态,进程才会在任意CPU上调度,也即运行该进程。Linux设备驱动程序让一个进程进入休眠状态很容易,但以安全的方式休眠,需要记住两条规则:
(1)永远不要在原子上下文中进入休眠,原子上下文指在执行多个步骤时,不能有任何的并发访问。这意味着,不能拥有自旋锁、seqlock、或者RCU锁时休眠。如果我们已经禁止了中断,也不能休眠。在拥有信号量休眠时是合法的,但必须确保有拥有信号量的并不会阻塞最终唤醒我们自己的那个进程。
(2)当被唤醒时,永远无法知道休眠期间都发生了什么,通常也无法知道是否还有其他进程在同一事件上休眠,这个进程可能会在我们之前被唤醒并将我们等待的资源拿走。这样,我们对唤醒之后的状态不能做任何假设,因此必须检查以确保我们等待的条件真正为真。

5.2.等待队列

Linux使用等待队列管理休眠的进程,一个等待队列通过一个“等待队列头”来管理。

    include <linux/wait.h>
    // 等待队列头类型
    typedef struct __wait_queue_head wait_queue_head_t;
    // 定义一个等待队列头并初始化
    #define DECLARE_WAIT_QUEUE_HEAD(name) wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)
    // 初始化一个等待队列头,q为等待队列头的指针
    init_waitqueue_head(q)	
    // 定义一个等待队列项并初始化,tsk表示属于那个进程,一般直接设置为current
    DECLARE_WAITQUEUE(name, tsk)
    // 向等待队列头添加等待队列项
    void add_wait_queue(wait_queue_head_t* q, wait_queue_t* wait)
    // 从等待队列头中移除等待队列项
    void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)

5.3.休眠方法

Linux内核中最简单的休眠方法是使用wait_event宏,在实现休眠的同时,也检查进程等待的条件。wq为等待队列头,值传递,condition为布尔表达式,表达式结果为假继续休眠,该条件可能会被多次求值,timeout为超时时间。

	include <linux/wait.h>
	// 不可中断休眠
	#define wait_event(wq, condition)
	// 可中断休眠(可被信号中断),返回值非0时表示被某个信号中断
	#define wait_event_interruptible(wq, condition)
	// 有限时间不可中断休眠,超时时间到期不管条件真假都返回,此时返回0
	#define wait_event_timeout(wq, condition, timeout)
	// 有限时间可中断休眠,超时时间到期不管条件真假都返回,此时返回0,
	// 返回值非0时表示被某个信号中断
	#define wait_event_interruptible_timeout(wq, condition, timeout)

5.4.唤醒方法

进程可以休眠,也必须可以唤醒,用来唤醒的基本函数是wake_upx为等待队列头的指针。

	// 唤醒给定等待队列上的所有非独占休眠进程
	#define wake_up(x)
	// 唤醒给定等待队列上的可中断的非独占休眠进程
	#define wake_up_interruptible(x)

5.5.阻塞和非阻塞操作

Linux I/O操作默认是阻塞的。如果应用想非阻塞调用,必须显出的指出,可在open设备的时候通过O_NONBLOCK标志决定,此标志在中定义,自动在中包含,当使用了O_NONBLOCK标志,系统调用会立刻返回,不管调用是否成功。驱动程序中,可通过检测O_NONBLOCK标志,确定应用调用的是阻塞IO还是非阻塞IO,然后采取不同的措施。
使用非阻塞的I/O的应用程序也经常使用pollselectepoll系统调用。pollselectepoll的功能本质上是一致的,都允许进程对一个或者多个打开的文件做非阻塞的读取或写入,这些调用本身会阻塞,直到给定的文件描述符集合中的任何一个可读取或者可写入。上述系统调用需要设备驱动程序支持,所有三个系统调用均通过驱动程序的poll方法提供,原型如下,位于struct file_operations结构体中。

	include <linux/fs.h>
	unsigned int (*poll) (struct file *, struct poll_table_struct *);    

poll函数分为两步处理:
(1)在一个或多个可指示poll状态变化的等待队列上调用poll_wait。如果当前没有文件描述符可用来执行I/O,则内核将使进程在传递该系统调用的所有文件描述符对应的等待队列上等待。
(2)返回一个用来描述操作是否可以立即无阻塞执行的位验码。
驱动程序中,需要调用poll_wait函数,将等待队列添加到poll_table结构中。filp 为文件指针,wait_address为等待队列头指针,ppoll_table结构指针。

	include <linux/poll.h>
	void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
	typedef struct poll_table_struct {
		poll_queue_proc _qproc;
		unsigned long _key;
	} poll_table; // struct poll_table_struct与poll_table一样
	#define POLLIN	     0x0001 // 设备可以无阻塞的读取
	#define POLLRDNORM	 0x0002 // 数据已经就绪,可以读取,可与POLLIN一起使用
	#define POLLOUT	     0x0004 // 设备可以无阻塞低写入
	#define POLLERR	     0x0008 // 设备发生了错误
	#define POLLHUP	     0x0010 // 读取设备的进程到达了文件尾
	#define POLLWRNORM   0x0020 // 数据已经就绪,可以写入,可与POLLOUT一起使用

6.信号驱动

readwritepollselectepoll等系统调用属于同步系统调用,都是应用主动调用系统调用进入内核,轮询资源是否可用。在有些情况下,这种轮询方式并不合适。异步通知不需要应用主动轮询,只要做一些设置,在资源可用时,应用会收到信号。

	include <linux/fs.h>
	// struct file_operations结构体成员,需要驱动实现此函数
	int (*fasync) (int, struct file *, int);
	// 当应用修改打开文件的FASYNC标志,就会调用此函数从相关的进程列表中增加或删除文件
	// 直白的说驱动程序调用此函数记录信号发给谁
	int fasync_helper(int, struct file *, int, struct fasync_struct **);
	// 发出信号,通知所有相关进程,驱动程序一般在中断中调用此函数
	void kill_fasync(struct fasync_struct **, int, int);

7.字符设备驱动源码

	/*===========================my_key.h================================*/
	#include 
	#include 
	#include 
	#include 
	#include 
	#include 
	
	// GPIO5寄存器地址定义
	#define GPIO5_DR  		(0x020AC000)  // 数据寄存器
	#define GPIO5_DRIR  	(0x020AC004)  // 方向寄存器,0为输入,1为输出
	#define GPIO5_PSR  		(0x020AC008)  // 状态寄存器
	#define GPIO5_ICR1  	(0x020AC00C)  // 中断配制寄存器1
	#define GPIO5_ICR2  	(0x020AC010)  // 中断配制寄存器2
	#define GPIO5_ICRIMR    (0x020AC014)  // 中断掩码寄存器
	#define GPIO5_ICRISR    (0x020AC018)  // 中断状态寄存器
	#define GPIO5_EDGE_SEL  (0x020AC01C)  // 边缘选择寄存器,用以配制双边沿触发中断
	#define GPIO5_1_MASK  (1 << 1)  // GPIO5_1掩码(SNVS_TAMPER1)
	// GPIO5_1引脚复用控制寄存器
	#define IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER1  (0x2290000 + 0xC)
	// GPIO5_1引脚复用模式为ATL5					
	#define ATL5  (5)
	#define GPIO5_1_MUX_MODE  ATL5
	#define GPIO5_1_MUX_MODE_MASK (0xF)
	// GPIO5_1引脚控制寄存器,配制电气属性,如上下拉等
	#define IOMUXC_SNVS_SW_PAD_CTL_PAD_SNVS_TAMPER1  (0x2290000 + 0x50)
	// GPIO5时钟控制寄存器
	#define CCM_CCGR1  (0x20C4000 + 0x6C)
	#define GPIO5_CLK_MASK  (3 << 30)
	// imx平台gpio转化为整数打宏定义,在gpio_to_irq用到
	#define IMX_GPIO_NR(bank, nr)	(((bank) - 1) * 32 + (nr))
	
	// 添加__iomem标记,编译器会检查此地址,确保在io空间
	struct key_ctl_pin
	{
		unsigned int __iomem* dr_pin;
		unsigned int __iomem* drir_pin;
		unsigned int __iomem* edge_sel_pin;
		unsigned int __iomem* mux_pin;
		unsigned int __iomem* ctl_pin;
		unsigned int __iomem* clk_pin;
	};
	
	// 设备结构体
	struct my_key_dev {
		struct cdev cdev;           // 字符设备结构体
	    struct key_ctl_pin pin;     // key 寄存器映射
		dev_t devno;                // 设备号
		struct class* my_key_class;
		struct device* my_key_device;
	    struct mutex mutex;         // 用于同步的互斥体
		volatile unsigned int key_push_cnt;  // 按键按下的次数
		struct timer_list timer;    // 定时器
		wait_queue_head_t wq;       // 等待队列头
		struct fasync_struct* fasync; // 异步通知结构体
		unsigned int irq;           // 虚拟中断号
		volatile int ev_press;      // 按键按下标志
		volatile char key_val;      // 按键值
	};
	/*===========================my_key.c================================*/
	#include 
	#include 
	#include 
	#include 
	#include 
	#include 
	#include 
	#include 
	#include 
	#include   
	#include 
	#include  
	#include 
	#include "my_key.h"
	
	static struct my_key_dev* my_key = NULL;
	/******************************读取数据函数**********************************/
	static ssize_t my_key_read(struct file* filp, char __user* buf, size_t size, loff_t* ppos)
	{
		struct my_key_dev* dev = filp->private_data;
		int ret;
		char val;
		if (size != sizeof(val)) return -EINVAL; 
	
		// 如果是非阻塞打开,判断是否有按键按下,有则尝试读取按键值,没有则直接返回错误
		if (filp->f_flags & O_NONBLOCK) {
			if (1 != dev->ev_press)
				return -EAGAIN;
		} else {
			ret = wait_event_interruptible(dev->wq, dev->ev_press); // 阻塞打开,等待被唤醒
			if (signal_pending(current))  // 判断是否是信号引起的唤醒
				ret = -ERESTARTSYS;
			if (0 != ret) return ret;
		}
		val = dev->key_val;
		ret = copy_to_user(buf, &val, sizeof(val)); 
		dev->ev_press = 0;
		if (0 != ret) return -EFAULT;
		return sizeof(val);
	}
	/*********************按键中断服务函数,在中断的上半段执行***********************/
	irqreturn_t my_key_irq_handler(int irq, void* dev)
	{
		struct my_key_dev* key = (struct my_key_dev*)dev;
		mod_timer(&key->timer, jiffies + msecs_to_jiffies(10)); // 定时器延时10毫秒
		return IRQ_RETVAL(IRQ_HANDLED);
	}
	/******************************定时器到期执行函数****************************/
	void timer_fun(unsigned long data)
	{
		int val = 0;
		struct my_key_dev* dev = (struct my_key_dev*)data;
		val = ioread32(dev->pin.dr_pin);
		val &= GPIO5_1_MASK;
		if (val == GPIO5_1_MASK) 
			dev->key_val = 1;  // 高电平,按键按下
		else
			dev->key_val = 0;  // 低电平,按键松开
		dev->ev_press = 1;
		if (0 != waitqueue_active(&dev->wq))  // 检查是否有进程在等待队列上休眠
			wake_up_interruptible(&dev->wq);  // 唤醒休眠的进程
		if (NULL != dev->fasync)  			  // 检查信号列表中是否进程在等待信号
			kill_fasync (&dev->fasync, SIGIO, POLL_IN);  // 发信号
		dev->key_push_cnt++;
	}
	/*************************初始化按键相关寄存器***********************/
	static void reg_set(struct my_key_dev* dev)
	{
		unsigned int val = 0;
		val = ioread32(dev->pin.mux_pin);
		val &= GPIO5_1_MUX_MODE_MASK;
		val |= GPIO5_1_MUX_MODE;
		iowrite32(val, dev->pin.mux_pin);  // 复用为GPIO5_1
	
		val = ioread32(dev->pin.drir_pin);
		val &= GPIO5_1_MASK;
		iowrite32(val, dev->pin.drir_pin);  // 设置为输入
	
		val = ioread32(dev->pin.clk_pin);
		val |= GPIO5_CLK_MASK;
		iowrite32(val, dev->pin.clk_pin);  // 开启GPIO5的时钟
	}
	/*****************打开设备函数,只用于一个进程或者线程打开********************/
	static int my_key_open(struct inode* inode, struct file* filp)
	{
		unsigned int ret = 0;
		struct my_key_dev* dev = container_of(inode->i_cdev, struct my_key_dev, cdev); 
		// 如果是非阻塞打开,尝试获取互斥体,获取失败直接返回,获取成功继续执行
		if (filp->f_flags & O_NONBLOCK) {
			// mutex_trylock和down_trylock的返回值意义相反
			if (!mutex_trylock(&dev->mutex))
				return -EBUSY;
		}
		else mutex_lock(&dev->mutex);
	 
		reg_set(dev);
	
		// 根据GPIO引脚申请虚拟中断号,IMX_GPIO_NR将GPIO5_1转换成一个32位整数
		dev->irq = gpio_to_irq(IMX_GPIO_NR(5,1));
		// 申请中断,双边沿触发,需要注意的是dev->irq为虚拟中断号,不是硬件中断号
		// 由于GPIO5 0-15引脚共享硬件中断号74,且系统已经使用了GPIO5_3作为心跳led的中断
		// 因此这里还要设置成共享中断
		ret = request_irq(dev->irq, my_key_irq_handler, IRQF_TRIGGER_RISING |   \
					IRQF_TRIGGER_FALLING | IRQF_SHARED, "my_key", dev);
		if (0 != ret) {
			printk(KERN_ERR "request_irq error\n");
			return -EAGAIN;
		}
		filp->private_data = dev;
		printk(KERN_INFO "my_key module open OK\n");
		return 0;
	}
	/***********************驱动实现的poll函数,用于轮询************************/
	static unsigned int my_key_poll(struct file* filep, poll_table* wait)
	{
		unsigned int mask = 0;
		struct my_key_dev* dev = filep->private_data;
		poll_wait(filep, &dev->wq, wait);
		if (1 == dev->ev_press)  // 如有按键按下,则表示有数据读取,返回POLLIN和POLLRDNORM标志
			mask = (POLLIN | POLLRDNORM);
		return mask;
	}
	/*******************驱动实现的fasync函数,实现异步通知功能**********************/
	static int my_key_fasync(int fd, struct file* filep, int on)
	{
		struct my_key_dev* dev = filep->private_data;
		return fasync_helper(fd, filep, on, &dev->fasync);
	}
	/*******************释放设备的函数,应用调用close时调用此函数******************/
	static int my_key_release(struct inode* inode, struct file* filep)
	{
		struct my_key_dev* dev = container_of(inode->i_cdev, struct my_key_dev, cdev); 
		free_irq(dev->irq, dev);  // 释放中断
		my_key_fasync(-1, filep, 0);  // 从异步通知列表中删除filep
		mutex_unlock(&dev->mutex);  // 关闭设备时释放互斥体
		return 0;
	}
	// 文件操作函数结构体
	static const struct file_operations my_key_fops = {
		.owner = THIS_MODULE,
		.open = my_key_open,
		.read = my_key_read,
		.poll = my_key_poll,
		.fasync = my_key_fasync,
		.release = my_key_release,
	};
	/************************映射控制三个led灯所需的寄存器***************************/
	static int key_ioremap(struct my_key_dev* dev)
	{
		int ret = 0;
		// 映射GPIO数据寄存器和方向控制寄存器
		dev->pin.dr_pin = ioremap(GPIO5_DR, 4 * 8);
		dev->pin.drir_pin = dev->pin.dr_pin + 1;
		dev->pin.edge_sel_pin = dev->pin.dr_pin + 7;
		// 映射引脚复用控制寄存器和引脚控制寄存器
		dev->pin.mux_pin = ioremap(IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER1, 4);
		dev->pin.ctl_pin = ioremap(IOMUXC_SNVS_SW_PAD_CTL_PAD_SNVS_TAMPER1, 4);
		// 映射时钟控制寄存器
		dev->pin.clk_pin = ioremap(CCM_CCGR1, 4);
		if ((NULL == dev->pin.dr_pin) || (NULL == dev->pin.mux_pin) || 
		    (NULL == dev->pin.ctl_pin) || (NULL == dev->pin.clk_pin)) {
			ret = -1;
			printk(KERN_ERR "key_ioremap() failed\n"); 
		}
		return ret;	
	}
	/********************注销已映射的key控制寄存器************************/
	static void key_iounmap(struct my_key_dev* dev)
	{
		iounmap(dev->pin.dr_pin);
		iounmap(dev->pin.mux_pin);
		iounmap(dev->pin.ctl_pin);
		iounmap(dev->pin.clk_pin);
	}
	/*******************初始化和注册cdev结构体****************************/
	static int set_up_my_key_cdev(struct my_key_dev* dev, int cnt)
	{
		int err;
		cdev_init(&dev->cdev, &my_key_fops);
		dev->cdev.owner = THIS_MODULE;
		err = cdev_add(&dev->cdev, dev->devno, cnt); // 出错返回负值
		if (err < 0)
			printk(KERN_ERR "adding my_key cdev %d error, errno %d\n", cnt, err);
		return err;
	}
	/*******************************定义设备的属性********************************/
	static ssize_t key_show(struct device* dev, struct device_attribute* attr,char* buf)
	{
		return sprintf(buf, "pressed button count %u\n", my_key->key_push_cnt);
	} 
	// 设备属性文件名为key,属性结构体名称为dev_attr_key,类型为struct device_attribute
	// 第二个模式参数要和show store函数匹配,如模式可读可写,则show store函数必须都要提供
	static DEVICE_ATTR(key, S_IRUSR | S_IRGRP | S_IROTH, key_show, NULL);
	
	/***************************模块初始化**************************************/
	static int __init my_key_init(void)
	{
		int ret = 0;
		dev_t devno = 0;
		// 动态分配设备号,传入的devno参数为0,使用unregister_chrdev_region注销动态分配的设备号
		ret = alloc_chrdev_region(&devno, 0, 1, "my_key");
		if (ret < 0) {
			printk(KERN_ERR "alloc_chrdev_region() failed %d\n", ret);
			return ret;	
		}
		// 分配设备结构体内存并将分配的内存清0
		my_key = kzalloc(sizeof(struct my_key_dev), GFP_KERNEL);
		if (NULL == my_key) {
			ret = -ENOMEM;
			printk(KERN_ERR "kzalloc() failed %d\n", ret);
			goto unreg_chrdev;
		}
		my_key->devno = devno;
		my_key->ev_press = 0;
		my_key->key_val = 0;
		init_timer(&my_key->timer);  // 初始化定时器
		my_key->timer.function = timer_fun;
		my_key->timer.data = (unsigned long)my_key;
		add_timer(&my_key->timer);   // 注册定时器
		init_waitqueue_head(&my_key->wq);  // 初始化等待队列头
	
		// 使用ioremap映射控制key所需的寄存器
		if (0 != key_ioremap(my_key)) { ret = -ENOMEM; goto unreg_chrdev; }
	
		ret = set_up_my_key_cdev(my_key, 1);
		if (ret < 0) goto iounmap_key;
	
		// 创建类和设备,当模块加载后会自动在/dev目录下生成设备节点
		my_key->my_key_class = class_create(THIS_MODULE, "my_key_class");
		if (IS_ERR(my_key->my_key_class)) {
			ret = PTR_ERR(my_key->my_key_class);
			printk(KERN_ERR "class_create() failed %d\n", ret);
			goto del_cdev;
		}
		my_key->my_key_device = device_create(my_key->my_key_class, NULL, devno, 
								NULL,"my_key");
		if (IS_ERR(my_key->my_key_device)) {
			ret = PTR_ERR(my_key->my_key_device);
			printk(KERN_ERR "device_create() failed %d\n", ret);
			goto clean_class;
		}
		// 使用device_create_file创建属性文件,一次只能创建一个
		// 使用device_remove_file移除属性文件
		ret = device_create_file(my_key->my_key_device, &dev_attr_key);
		if (ret != 0) goto clean_device;
	
		// 初始化互斥体
		mutex_init(&my_key->mutex);
		printk(KERN_INFO "my_key module init OK, major %u, minor %u\n", 
						MAJOR(devno), MINOR(devno));
		return 0;
	clean_device:
		device_destroy(my_key->my_key_class, devno);
	clean_class: 
		class_destroy(my_key->my_key_class);
	del_cdev:
		cdev_del(&my_key->cdev);
	iounmap_key:	
		key_iounmap(my_key);
		kfree(my_key);
		my_key = NULL;
	unreg_chrdev:
		unregister_chrdev_region(devno, 1);
	    return ret;
	}
	/********************模块注销************************/
	static void __exit my_key_exit(void)
	{
		device_remove_file(my_key->my_key_device, &dev_attr_key);
		device_destroy(my_key->my_key_class, my_key->devno);
		class_destroy(my_key->my_key_class);
		cdev_del(&my_key->cdev);
		del_timer(&my_key->timer);   // 删除定时器
		key_iounmap(my_key);
		unregister_chrdev_region(my_key->devno, 1);
		kfree(my_key);
		my_key = NULL;
		printk(KERN_INFO "my_key module exit\n");
	}
	
	module_init(my_key_init);
	module_exit(my_key_exit);
	
	MODULE_LICENSE("GPL");
	MODULE_AUTHOR("[email protected]");
	MODULE_VERSION("v1.00");

8.测试程序源码

	#include 
	#include 
	#include 
	#include 
	#include 
	#include 
	#include 
	#include 
	#include 
	#define PATH "/dev/my_key"
	#define OPT_STRING    ":a:"   // 选项字符串
	static void print_usage()
	{
	    printf("Usage:    ./test -a \n"
	           "-a:       running mode\n"
	           "b:        block\n"
	           "nb:       non-block\n" 
	           "poll:     poll \n"
	           "select:   select\n"
	           "signal:   asynchronous notification\n");
	}
	
	extern char* optarg;  // 指向参数值
	extern int optind;    // 记录getopt函数处理argv[]数组的位置,一般不需要设置
	extern int opterr;    // 保存出错信息,非0 getopt会向stderr打印出错信息,如为0时则不打印
	/* 遇到无法识别的选项,getopt返回?号,并将?存储到optopt中,如果将选项字符串第一个字符设置
	   为:号,那么getopt函数在用户未提供值的情况下返回:号而不是?号 */
	extern int optopt; 
	static int parse_option(int argc, char* argv[], int* mode)
	{
	    int opt;
	    if (3 != argc) {
	        print_usage();
	        return -1;       
	    }
	    while (-1 != (opt = getopt(argc, argv, OPT_STRING))) {
	        switch (opt) {
	        case 'a':
	            if ('b' == *optarg) mode = 0;
	            else if (0 == strcmp(optarg, "nb")) *mode = 1;
	            else if (0 == strcmp(optarg, "poll")) *mode = 2; 
	            else if (0 == strcmp(optarg, "select")) *mode = 3;
	            else if (0 == strcmp(optarg, "signal")) *mode = 4;
	            else return -1; 
	            break;
	        case ':':
	            print_usage();
	            return -1;
	        case '?':
	            print_usage();
	            return -1;
	
	        default:
	            print_usage();
	            return -1;
	        }
	    }
	    return 0;
	}
	static int fd;
	static unsigned int push_cnt = 0, release_cnt = 0;
	static void read_key(int fd)
	{
	    int len;
	    char val = 0xFF;         
	    len = read(fd, &val, sizeof(val));
	    if (sizeof(val) == len) {
	        if (1 == val) {
	            push_cnt++;
	            printf("push the button %d\n", push_cnt);
	        } 
	        if (0 == val) {
	            release_cnt++;
	            printf("release the button %d\n", release_cnt);
	        }
	    }
	}
	// 信号处理函数
	static void my_signal_fun(int signum)
	{
	    read_key(fd);
	}
	
	int main(int argc, char* argv[])
	{
	    int len, ret;
	    int flags = 0, mode = 0;
	    char val = 0xFF;
	    struct pollfd fds = {0};
	    struct timeval timeout = {0};
	    fd_set read_fds;
	    ret = parse_option(argc, argv, &mode);
	    if (0 != ret) return -1;
	
	    switch (mode) {
	    case 0:
	        fd = open(PATH, O_RDONLY); // 阻塞打开
	        if (fd < 0){
	            printf("my_key open error\n");
	            return -1;
	        }
	        while (1) read_key(fd);
	        break;
	    case 1:
	        fd = open(PATH, O_RDONLY | O_NONBLOCK);  // 非阻塞打开
	        if (fd < 0){
	            printf("my_key open error\n");
	            return -1;
	        }
	        while (1) read_key(fd);
	        break;
	    case 2:
	        fd = open(PATH, O_RDONLY); 
	        if (fd < 0){
	            printf("my_key open error\n");
	            return -1;
	        }
	        fds.fd = fd;
	        fds.events = (POLLIN | POLLRDNORM);
	        while (1) {
	            //fds.revents = 0;
	            ret = poll(&fds, 1, 4000);  // 超时时间设置为4秒,
	            if (ret < 0) {
	                printf("poll error\n");
	                close(fd);
	                return -1;
	            } 
	            else if ((fds.revents & POLLIN) || (fds.revents & POLLRDNORM))
	                read_key(fd);
	            else printf("poll timeout\n");
	        }
	        break;
	    case 3:
	        FD_ZERO(&read_fds);
	        fd = open(PATH, O_RDONLY);
	        if (fd < 0){
	            printf("my_key open error\n");
	            return -1;
	        }
	        while (1) {
	            FD_SET(fd, &read_fds);      
	            timeout.tv_usec = 0;           
	            timeout.tv_sec = 4;  // 超时时间设置为4秒
	            ret = select(fd + 1, &read_fds, NULL, NULL, &timeout);
	            if (ret < 0) {
	                printf("select error\n");
	                close(fd);
	                return -1;
	            } 
	            else if (FD_ISSET(fd, &read_fds)) 
	                read_key(fd);
	            else printf("select timeout\n");
	        }
	        break;
	    case 4:
	        fd = open(PATH, O_RDONLY); 
	        if (fd < 0){
	            printf("my_key open error\n");
	            return -1;
	        }
	        fcntl(fd, F_SETOWN, getpid());
	        flags = fcntl(fd, F_GETFL);
	        fcntl(fd, F_SETFL, flags | FASYNC);
	        signal(SIGIO, my_signal_fun); 
	        while (1) sleep(4);
	        break;
	    default:
	        break;
	    }
	    close(fd);
	    return 0;
	}

你可能感兴趣的:(Linux设备驱动,Linux,Linux字符设备驱动,中断,阻塞非阻塞IO,信号驱动IO)