第一、二期銜接——4.7 字符驅動設備之按鍵驅動—解決同步互斥阻塞

編寫按鍵中斷驅動——解決同步互斥阻塞

  • 硬件平臺:韋東山嵌入式Linxu開發板(S3C2440.v3)
  • 軟件平臺:運行於VMware Workstation 12 Player下UbuntuLTS16.04_x64 系統
  • 參考資料:《嵌入式Linux應用開發手冊》、《嵌入式Linux應用開發手冊第2版》
  • 開發環境:Linux 2.6.22.6 內核、arm-linux-gcc-3.4.5-glibc-2.3.6工具鏈


一、前言

在上邊的應用程序中,存在一個問題:同一個時間中,可以打開兩個或以上的應用程序。
提問:那如果我們想要在同一時間中,只能打開一個應用程序該如何修改呢?
回答:在驅動程序中定義static int canopen = 1;
button_drv_open函數中添加:

/* canopen != 0說明文件已經被打開,返回-EBUSY */
	if(--canopen != 0){
		canopen++;
		return -EBUSY;
	}

button_drv_close函數中添加:

canopen++;	/* 如果打開文件,關閉時canopen恢復 */

通過測試,發現這個程序可以達到上面的目的,但是還是有問題:
在這裏插入圖片描述
對於C語言,程序最終會被翻譯成彙編,一條--canopen指令會被翻譯成3條彙編指令(讀出、修改、寫回),如果一個A應用程序任務在執行讀出時,此時切換到B應用程序,也執行讀出命令(A、B是同一個應用程序),那麼最終A、B都可以同時存在

二、引入原子操作

1、設置原子變量

static atomic_t canopen = ATOMIC_INIT(1);     //定義原子變量v並初始化爲1

2、在button_drv_open中採用原子操作

	/* canopen != 0說明文件已經被打開,返回-EBUSY 
	 * 自減操作後測試其是否爲0,爲0則返回true,否則返回false
	 */
	if(!atomic_dec_and_test(&canopen)){
		atomic_inc(&canopen);    //原子變量增加1
		return -EBUSY;
	}

所以此時就引出了一個互斥的話題

3、在button_drv_close中採用原子操作

	atomic_inc(&canopen);    //原子變量增加1

4、完整驅動代碼

#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>
#include <linux/poll.h>

int major;
static struct class *buttondrv_class;
static struct class_device	*buttondrv_class_dev;

volatile unsigned long *gpfcon;
volatile unsigned long *gpfdat;
volatile unsigned long *gpgcon;
volatile unsigned long *gpgdat;

static struct fasync_struct *button_async;

static atomic_t canopen = ATOMIC_INIT(1);     //定義原子變量v並初始化爲1


/* 鍵值: 按下時, 0x01, 0x02, 0x03, 0x04 
 * 鍵值: 鬆開時, 0x81, 0x82, 0x83, 0x84 
 */
static unsigned char key_val;

/* 按鍵信息 */
struct pin_desc{
	unsigned int pin;
	unsigned int key_val;
};

/* 存儲4個按鍵的信息 */
struct pin_desc pins_desc[4] = {
 	{S3C2410_GPF0,  0x01},
 	{S3C2410_GPF2,  0x02},
 	{S3C2410_GPG3,  0x03},
	{S3C2410_GPG11, 0x04},
};

/* 中斷註冊信息 */
struct buttonirq_decs{
	unsigned int irq;
	unsigned long flags;
	const char *devname;
	void *dev_id;
};

struct buttonirq_decs buttonirqs_decs[4] = {
	{IRQ_EINT0,	 IRQT_BOTHEDGE, "S2", &pins_desc[0]},
	{IRQ_EINT2,	 IRQT_BOTHEDGE, "S3", &pins_desc[1]},
	{IRQ_EINT11, IRQT_BOTHEDGE, "S4", &pins_desc[2]},
	{IRQ_EINT19, IRQT_BOTHEDGE, "S5", &pins_desc[3]},
};

/* 生成一個等待隊列的隊頭,名字爲button_waitq */
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);

/* 中斷事件標誌, 中斷服務程序將它置1,button_drv_read將它清0 */
static volatile int ev_press = 0;

/* 中斷服務函數 */
static irqreturn_t button_irq(int irq, void *dev_id)
{
	struct pin_desc *pindesc = (struct pin_desc *)dev_id;
	unsigned int pinval;

	pinval = s3c2410_gpio_getpin(pindesc->pin);	//讀取IO口電平

	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);	//發SIGIO信號
	
	return IRQ_RETVAL(IRQ_HANDLED);
}

/* open驅動函數 */
static int button_drv_open(struct inode *inode, struct file *file)
{
	unsigned int i;
	int err;

	/* canopen != 0說明文件已經被打開,返回-EBUSY 
	 * 自減操作後測試其是否爲0,爲0則返回true,否則返回false
	 */
	if(!atomic_dec_and_test(&canopen)){
		atomic_inc(&canopen);    //原子變量增加1
		return -EBUSY;
	}

	/* 註冊中斷 */
	for(i = 0; i < (sizeof(buttonirqs_decs)/sizeof(buttonirqs_decs[0])); i++){
		err = request_irq(buttonirqs_decs[i].irq, button_irq, buttonirqs_decs[i].flags,
					buttonirqs_decs[i].devname, buttonirqs_decs[i].dev_id);
		if(err){
			printk("func button_drv_open err: request_irq num:%d\n", i);
			break;
		}
	}

	/* 出現錯誤,釋放已經註冊的中斷 */
	if(err){
        i--;
        for (; i >= 0; i--)
            free_irq(buttonirqs_decs[i].irq, buttonirqs_decs[i].dev_id);
        return -EBUSY;
    }	
	return 0;
}

/* read驅動函數 */
static ssize_t button_drv_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos)
{
	
	unsigned long ret = 0;
	int err;

	/* 如果沒有按鍵動作, 休眠 */
	err = wait_event_interruptible(button_waitq, ev_press);

	if (err){
			printk("func button_drv_read() err: wait_event_interruptible\n");
		return err;
	}
	

	/* 如果有按鍵動作, 返回鍵值 */
	ret = copy_to_user(buf, &key_val, 1);
	ev_press = 0;	//清中斷標誌
	
	if(ret < 0){
		printk("func button_drv_read() err: copy_to_user\n");
		return -EFAULT;
	}
	
 	return sizeof(key_val);
}

/* close驅動函數 */
int button_drv_close (struct inode *inode, struct file *file)
{
	int i;

	/* 取消中斷 */
	for(i = 0; i < (sizeof(buttonirqs_decs)/sizeof(buttonirqs_decs[0])); i++)
		free_irq(buttonirqs_decs[i].irq, buttonirqs_decs[i].dev_id);

	atomic_inc(&canopen);    //原子變量增加1
	return 0;
}

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

	/* 中斷已經發生 */
	if (ev_press)
		mask |= POLLIN | POLLRDNORM;

	return mask;	//返回給do_poll函數中的mask
}

/* 調用fasync_helper根據on值是否button_async->fa_file=驅動文件file,裏面有PID */
static int button_drv_fasync(int fd, struct file *file, int on)
{
	printk("driver: fifth_drv_fasync\n");
	return fasync_helper(fd, file, on, &button_async);
}

static struct file_operations button_drv_fops = {
	.owner   =  THIS_MODULE,    /* 這是一個宏,推向編譯模塊時自動創建的__this_module變量 */
    .open    =  button_drv_open,     
	.read    =	button_drv_read,
	.release =  button_drv_close,
	.poll    =  buton_drv_poll,
	.fasync	 =  button_drv_fasync,
};

/* 入口函數 
 * 執行”insmod button_drv.ko”命令時就會調用這個函數
 */
static int button_drv_init(void)
{
	//註冊一個字符設備,名字爲button_drv
	major = register_chrdev(0, "button_drv", &button_drv_fops);	
	
	//創建一個類,名字爲buttond_rv(/class/button_drv)
	buttondrv_class = class_create(THIS_MODULE, "button_drv");	
	
	//創建一個設備節點,名爲button(/dev/button)
	buttondrv_class_dev = class_device_create(buttondrv_class, NULL, MKDEV(major, 0), NULL, "button"); 

	/* 映射物理地址 */
	gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
	gpfdat = gpfcon + 1;

	gpgcon = (volatile unsigned long *)ioremap(0x56000060, 16);
	gpgdat = gpgcon + 1;
	
	return 0;
}

/* 出口函數 
 * 執行”rmmod button_drv.ko”命令時就會調用這個函數
 */
static void button_drv_exit(void)
{
	unregister_chrdev(major, "button_drv"); // 卸載字符

	class_device_unregister(buttondrv_class_dev);	//刪除設備節點
	class_destroy(buttondrv_class);	//銷燬類
	
	/* 取消映射 */
	iounmap(gpfcon);
	iounmap(gpgcon);
}

module_init(button_drv_init);
module_exit(button_drv_exit);

MODULE_LICENSE("GPL");

5、完整測試代碼

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


int fd;

/* 信號處理函數 */
void my_signal_fun(int signum)
{
	int ret; 
	unsigned char key_val;
	ret = read(fd, &key_val, 1);
	if(ret < 0)
		printf(" func read() err\n");
	else
		printf("key_val = 0x%x\n", key_val);
}

int main(int argc, char **argv)
{

	int Oflags;

	signal(SIGIO, my_signal_fun);	//給SIGIO 信號註冊信號處理函數my_signal_fun
	
	fd = open("/dev/button", O_RDWR);	
	if (fd < 0)
	{
		printf("can't open!\n");
	}

	fcntl(fd, F_SETOWN, getpid());	//把進程 ID 告訴驅動
		
	Oflags = fcntl(fd, F_GETFL);	//取得文件描述符filedes的文件狀態標誌
	fcntl(fd, F_SETFL, Oflags | FASYNC);	//FASYNC位發生變化時,會導致驅動程序的fasync函數被調用
	
	while (1)
	{
		sleep(1000);
	}

	return 0;
}


6、運行結果

可以看到,當打開同一個應用程序時,會打不開

二、引入信號量

信號量(semaphore)是用於保護臨界區的一種常用方法,只有得到信號量的進程才能執行臨界區代碼。當獲取不到信號量時,進程進入休眠等待狀態。

1、定義信號量

static DECLARE_MUTEX(button_lock);     //定義互斥鎖

2、在button_drv_open中獲取信號量

	/* 獲取信號量 
	 * 如果第一次獲取。則可以獲取一個信號量
	 * 如果第二次獲取。則在down()中陷入休眠
 	 */
	down(&button_lock);

3、在button_drv_close中釋放信號量

	up(&button_lock);		//釋放信號量

4、完整驅動代碼

#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>
#include <linux/poll.h>

int major;
static struct class *buttondrv_class;
static struct class_device	*buttondrv_class_dev;

volatile unsigned long *gpfcon;
volatile unsigned long *gpfdat;
volatile unsigned long *gpgcon;
volatile unsigned long *gpgdat;

static struct fasync_struct *button_async;

//static atomic_t canopen = ATOMIC_INIT(1);     //定義原子變量v並初始化爲1
static DECLARE_MUTEX(button_lock);     //定義互斥鎖





/* 鍵值: 按下時, 0x01, 0x02, 0x03, 0x04 
 * 鍵值: 鬆開時, 0x81, 0x82, 0x83, 0x84 
 */
static unsigned char key_val;

/* 按鍵信息 */
struct pin_desc{
	unsigned int pin;
	unsigned int key_val;
};

/* 存儲4個按鍵的信息 */
struct pin_desc pins_desc[4] = {
 	{S3C2410_GPF0,  0x01},
 	{S3C2410_GPF2,  0x02},
 	{S3C2410_GPG3,  0x03},
	{S3C2410_GPG11, 0x04},
};

/* 中斷註冊信息 */
struct buttonirq_decs{
	unsigned int irq;
	unsigned long flags;
	const char *devname;
	void *dev_id;
};

struct buttonirq_decs buttonirqs_decs[4] = {
	{IRQ_EINT0,	 IRQT_BOTHEDGE, "S2", &pins_desc[0]},
	{IRQ_EINT2,	 IRQT_BOTHEDGE, "S3", &pins_desc[1]},
	{IRQ_EINT11, IRQT_BOTHEDGE, "S4", &pins_desc[2]},
	{IRQ_EINT19, IRQT_BOTHEDGE, "S5", &pins_desc[3]},
};

/* 生成一個等待隊列的隊頭,名字爲button_waitq */
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);

/* 中斷事件標誌, 中斷服務程序將它置1,button_drv_read將它清0 */
static volatile int ev_press = 0;

/* 中斷服務函數 */
static irqreturn_t button_irq(int irq, void *dev_id)
{
	struct pin_desc *pindesc = (struct pin_desc *)dev_id;
	unsigned int pinval;

	pinval = s3c2410_gpio_getpin(pindesc->pin);	//讀取IO口電平

	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);	//發SIGIO信號
	
	return IRQ_RETVAL(IRQ_HANDLED);
}

/* open驅動函數 */
static int button_drv_open(struct inode *inode, struct file *file)
{
	unsigned int i;
	int err;

#if 0
	/* canopen != 0說明文件已經被打開,返回-EBUSY 
	 * 自減操作後測試其是否爲0,爲0則返回true,否則返回false
	 */
	if(!atomic_dec_and_test(&canopen)){
		atomic_inc(&canopen);    //原子變量增加1
		return -EBUSY;
	}
#endif

	/* 獲取信號量 
	 * 如果第一次獲取。則可以獲取一個信號量
	 * 如果第二次獲取。則在down()中陷入休眠
 	 */
	down(&button_lock);

	/* 註冊中斷 */
	for(i = 0; i < (sizeof(buttonirqs_decs)/sizeof(buttonirqs_decs[0])); i++){
		err = request_irq(buttonirqs_decs[i].irq, button_irq, buttonirqs_decs[i].flags,
					buttonirqs_decs[i].devname, buttonirqs_decs[i].dev_id);
		if(err){
			printk("func button_drv_open err: request_irq num:%d\n", i);
			break;
		}
	}

	/* 出現錯誤,釋放已經註冊的中斷 */
	if(err){
        i--;
        for (; i >= 0; i--)
            free_irq(buttonirqs_decs[i].irq, buttonirqs_decs[i].dev_id);
        return -EBUSY;
    }	
	return 0;
}

/* read驅動函數 */
static ssize_t button_drv_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos)
{
	
	unsigned long ret = 0;
	int err;

	/* 如果沒有按鍵動作, 休眠 */
	err = wait_event_interruptible(button_waitq, ev_press);

	if (err){
			printk("func button_drv_read() err: wait_event_interruptible\n");
		return err;
	}
	
	/* 如果有按鍵動作, 返回鍵值 */
	ret = copy_to_user(buf, &key_val, 1);
	ev_press = 0;	//清中斷標誌
	
	if(ret < 0){
		printk("func button_drv_read() err: copy_to_user\n");
		return -EFAULT;
	}
	
 	return sizeof(key_val);
}

/* close驅動函數 */
int button_drv_close (struct inode *inode, struct file *file)
{
	int i;

	/* 取消中斷 */
	for(i = 0; i < (sizeof(buttonirqs_decs)/sizeof(buttonirqs_decs[0])); i++)
		free_irq(buttonirqs_decs[i].irq, buttonirqs_decs[i].dev_id);

	//atomic_inc(&canopen);    //原子變量增加1
	up(&button_lock);		//釋放信號量
	return 0;
}

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

	/* 中斷已經發生 */
	if (ev_press)
		mask |= POLLIN | POLLRDNORM;

	return mask;	//返回給do_poll函數中的mask
}

/* 調用fasync_helper根據on值是否button_async->fa_file=驅動文件file,裏面有PID */
static int button_drv_fasync(int fd, struct file *file, int on)
{
	printk("driver: fifth_drv_fasync\n");
	return fasync_helper(fd, file, on, &button_async);
}

static struct file_operations button_drv_fops = {
	.owner   =  THIS_MODULE,    /* 這是一個宏,推向編譯模塊時自動創建的__this_module變量 */
    .open    =  button_drv_open,     
	.read    =	button_drv_read,
	.release =  button_drv_close,
	.poll    =  buton_drv_poll,
	.fasync	 =  button_drv_fasync,
};

/* 入口函數 
 * 執行”insmod button_drv.ko”命令時就會調用這個函數
 */
static int button_drv_init(void)
{
	//註冊一個字符設備,名字爲button_drv
	major = register_chrdev(0, "button_drv", &button_drv_fops);	
	
	//創建一個類,名字爲buttond_rv(/class/button_drv)
	buttondrv_class = class_create(THIS_MODULE, "button_drv");	
	
	//創建一個設備節點,名爲button(/dev/button)
	buttondrv_class_dev = class_device_create(buttondrv_class, NULL, MKDEV(major, 0), NULL, "button"); 

	/* 映射物理地址 */
	gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
	gpfdat = gpfcon + 1;

	gpgcon = (volatile unsigned long *)ioremap(0x56000060, 16);
	gpgdat = gpgcon + 1;
	
	return 0;
}

/* 出口函數 
 * 執行”rmmod button_drv.ko”命令時就會調用這個函數
 */
static void button_drv_exit(void)
{
	unregister_chrdev(major, "button_drv"); // 卸載字符

	class_device_unregister(buttondrv_class_dev);	//刪除設備節點
	class_destroy(buttondrv_class);	//銷燬類
	
	/* 取消映射 */
	iounmap(gpfcon);
	iounmap(gpgcon);
}

module_init(button_drv_init);
module_exit(button_drv_exit);

MODULE_LICENSE("GPL");

5、完整測試代碼

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

int fd;

/* 信號處理函數 */
void my_signal_fun(int signum)
{
	int ret; 
	unsigned char key_val;
	ret = read(fd, &key_val, 1);
	if(ret < 0)
		printf(" func read() err\n");
	else
		printf("key_val = 0x%x\n", key_val);
}

int main(int argc, char **argv)
{

	int Oflags;

	signal(SIGIO, my_signal_fun);	//給SIGIO 信號註冊信號處理函數my_signal_fun
	
	fd = open("/dev/button", O_RDWR);	
	if (fd < 0)
	{
		printf("can't open!\n");
	}

	fcntl(fd, F_SETOWN, getpid());	//把進程 ID 告訴驅動
		
	Oflags = fcntl(fd, F_GETFL);	//取得文件描述符filedes的文件狀態標誌
	fcntl(fd, F_SETFL, Oflags | FASYNC);	//FASYNC位發生變化時,會導致驅動程序的fasync函數被調用
	
	while (1)
	{
		sleep(1000);
	}

	return 0;
}

6、運行結果

在這裏插入圖片描述
在這裏插入圖片描述

四、阻塞與阻塞

  • 阻塞
    是指在執行設備操作時若不能獲得資源則掛起進程,直到滿足可操作的條件後再進行操作。
    被掛起的進程進入休眠狀態,被從調度器的運行隊列移走,直到等待的條件被滿足。
  • 非阻塞
    進程在不能進行設備操作時並不掛起,它或者放棄,或者不停地查詢,直至可以進行操作爲止。

1、添加非阻塞標誌

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

2、在button_drv_open中採用判斷採用何種方式

 	 if(file->f_flags & O_NONBLOCK){	//非阻塞
		if(down(&button_lock))		//申請信號量
			return -EBUSY;			//返回值給應用程序打印
	 }else
		down(&button_lock);	//阻塞方式,申請信號量

3、在button_drv_read中採用判斷採用何種方式

	if(file->f_flags & O_NONBLOCK){	//非阻塞
		if(!ev_press)				//沒有按鍵按下
		return -EAGAIN;				//返回
	}

4、完整驅動代碼

#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>
#include <linux/poll.h>

int major;
static struct class *buttondrv_class;
static struct class_device	*buttondrv_class_dev;

volatile unsigned long *gpfcon;
volatile unsigned long *gpfdat;
volatile unsigned long *gpgcon;
volatile unsigned long *gpgdat;

static struct fasync_struct *button_async;

//static atomic_t canopen = ATOMIC_INIT(1);     //定義原子變量v並初始化爲1
static DECLARE_MUTEX(button_lock);     //定義互斥鎖



/* 鍵值: 按下時, 0x01, 0x02, 0x03, 0x04 
 * 鍵值: 鬆開時, 0x81, 0x82, 0x83, 0x84 
 */
static unsigned char key_val;

/* 按鍵信息 */
struct pin_desc{
	unsigned int pin;
	unsigned int key_val;
};

/* 存儲4個按鍵的信息 */
struct pin_desc pins_desc[4] = {
 	{S3C2410_GPF0,  0x01},
 	{S3C2410_GPF2,  0x02},
 	{S3C2410_GPG3,  0x03},
	{S3C2410_GPG11, 0x04},
};

/* 中斷註冊信息 */
struct buttonirq_decs{
	unsigned int irq;
	unsigned long flags;
	const char *devname;
	void *dev_id;
};

struct buttonirq_decs buttonirqs_decs[4] = {
	{IRQ_EINT0,	 IRQT_BOTHEDGE, "S2", &pins_desc[0]},
	{IRQ_EINT2,	 IRQT_BOTHEDGE, "S3", &pins_desc[1]},
	{IRQ_EINT11, IRQT_BOTHEDGE, "S4", &pins_desc[2]},
	{IRQ_EINT19, IRQT_BOTHEDGE, "S5", &pins_desc[3]},
};

/* 生成一個等待隊列的隊頭,名字爲button_waitq */
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);

/* 中斷事件標誌, 中斷服務程序將它置1,button_drv_read將它清0 */
static volatile int ev_press = 0;

/* 中斷服務函數 */
static irqreturn_t button_irq(int irq, void *dev_id)
{
	struct pin_desc *pindesc = (struct pin_desc *)dev_id;
	unsigned int pinval;

	pinval = s3c2410_gpio_getpin(pindesc->pin);	//讀取IO口電平

	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);	//發SIGIO信號
	
	return IRQ_RETVAL(IRQ_HANDLED);
}

/* open驅動函數 */
static int button_drv_open(struct inode *inode, struct file *file)
{
	unsigned int i;
	int err;

#if 0
	/* canopen != 0說明文件已經被打開,返回-EBUSY 
	 * 自減操作後測試其是否爲0,爲0則返回true,否則返回false
	 */
	if(!atomic_dec_and_test(&canopen)){
		atomic_inc(&canopen);    //原子變量增加1
		return -EBUSY;
	}
#endif
	
 	 if(file->f_flags & O_NONBLOCK){	//非阻塞
		if(down_trylock(&button_lock))		//申請信號量
			return -EBUSY;			//不成功,返回值給應用程序打印
	 }else
		down(&button_lock);	//阻塞方式,申請信號量

	/* 註冊中斷 */
	for(i = 0; i < (sizeof(buttonirqs_decs)/sizeof(buttonirqs_decs[0])); i++){
		err = request_irq(buttonirqs_decs[i].irq, button_irq, buttonirqs_decs[i].flags,
					buttonirqs_decs[i].devname, buttonirqs_decs[i].dev_id);
		if(err){
			printk("func button_drv_open err: request_irq num:%d\n", i);
			break;
		}
	}

	/* 出現錯誤,釋放已經註冊的中斷 */
	if(err){
        i--;
        for (; i >= 0; i--)
            free_irq(buttonirqs_decs[i].irq, buttonirqs_decs[i].dev_id);
        return -EBUSY;
    }	
	return 0;
}

/* read驅動函數 */
static ssize_t button_drv_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos)
{
	
	unsigned long ret = 0;
	int err;

	if(file->f_flags & O_NONBLOCK){	//非阻塞
		if(!ev_press)				//沒有按鍵按下
		return -EAGAIN;				//返回
	}
	
	/* 阻塞方式,如果沒有按鍵動作, 休眠 */
	err = wait_event_interruptible(button_waitq, ev_press);

	if (err){
			printk("func button_drv_read() err: wait_event_interruptible\n");
		return err;
	}


	/* 如果有按鍵動作, 返回鍵值 */
	ret = copy_to_user(buf, &key_val, 1);
	ev_press = 0;	//清中斷標誌
	
	if(ret < 0){
		printk("func button_drv_read() err: copy_to_user\n");
		return -EFAULT;
	}
	
 	return sizeof(key_val);
}

/* close驅動函數 */
int button_drv_close (struct inode *inode, struct file *file)
{
	int i;

	/* 取消中斷 */
	for(i = 0; i < (sizeof(buttonirqs_decs)/sizeof(buttonirqs_decs[0])); i++)
		free_irq(buttonirqs_decs[i].irq, buttonirqs_decs[i].dev_id);

	//atomic_inc(&canopen);    //原子變量增加1
	up(&button_lock);		//釋放信號量
	return 0;
}

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

	/* 中斷已經發生 */
	if (ev_press)
		mask |= POLLIN | POLLRDNORM;

	return mask;	//返回給do_poll函數中的mask
}

/* 調用fasync_helper根據on值是否button_async->fa_file=驅動文件file,裏面有PID */
static int button_drv_fasync(int fd, struct file *file, int on)
{
	printk("driver: fifth_drv_fasync\n");
	return fasync_helper(fd, file, on, &button_async);
}

static struct file_operations button_drv_fops = {
	.owner   =  THIS_MODULE,    /* 這是一個宏,推向編譯模塊時自動創建的__this_module變量 */
    .open    =  button_drv_open,     
	.read    =	button_drv_read,
	.release =  button_drv_close,
	.poll    =  buton_drv_poll,
	.fasync	 =  button_drv_fasync,
};

/* 入口函數 
 * 執行”insmod button_drv.ko”命令時就會調用這個函數
 */
static int button_drv_init(void)
{
	//註冊一個字符設備,名字爲button_drv
	major = register_chrdev(0, "button_drv", &button_drv_fops);	
	
	//創建一個類,名字爲buttond_rv(/class/button_drv)
	buttondrv_class = class_create(THIS_MODULE, "button_drv");	
	
	//創建一個設備節點,名爲button(/dev/button)
	buttondrv_class_dev = class_device_create(buttondrv_class, NULL, MKDEV(major, 0), NULL, "button"); 

	/* 映射物理地址 */
	gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
	gpfdat = gpfcon + 1;

	gpgcon = (volatile unsigned long *)ioremap(0x56000060, 16);
	gpgdat = gpgcon + 1;
	
	return 0;
}

/* 出口函數 
 * 執行”rmmod button_drv.ko”命令時就會調用這個函數
 */
static void button_drv_exit(void)
{
	unregister_chrdev(major, "button_drv"); // 卸載字符

	class_device_unregister(buttondrv_class_dev);	//刪除設備節點
	class_destroy(buttondrv_class);	//銷燬類
	
	/* 取消映射 */
	iounmap(gpfcon);
	iounmap(gpgcon);
}

module_init(button_drv_init);
module_exit(button_drv_exit);

MODULE_LICENSE("GPL");

5、完整測試代碼

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

int main(int argc, char **argv)
{
	int ret; 
	unsigned char key_val;
	int fd;
	
	fd = open("/dev/button", O_RDWR | O_NONBLOCK);	
	if (fd < 0)
	{
		printf("can't open!\n");
	}
	
	while (1)
	{
		ret = read(fd, &key_val, 1);
		printf("key_val = 0x%x ret = %d\n", key_val, ret);
	}

	return 0;
}

6、運行結果

可以看到

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章