特殊按键--休眠键驱动

这是一个关于休眠和关机的按键驱动。板子:pxa31X系列  内核:2.6.25
这个驱动用到了内核文件操作,内核线程,等待队列,异步通知,并介绍了一种调试驱动的方法。
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/syscalls.h>
#include <linux/unistd.h>
#include <linux/miscdevice.h>
#include <linux/platform_device.h>
#include <linux/uaccess.h>
#include <linux/string.h>
#include <asm/arch/pxa-regs.h>
#include <asm/arch/pxa3xx-regs.h>
#include <asm/arch/mfp-pxa300.h>
#include <asm/arch/gpio.h>
#include <asm/uaccess.h>    //用于内核线程
#include <linux/irq.h> 
#include <linux/interrupt.h>
#include <linux/ioctl.h>
#include <linux/sched.h>
#include <linux/kthread.h>
#include <linux/errno.h>
#include <linux/spinlock.h>
#include <linux/mutex.h>
#include <linux/wait.h>
#include <asm/semaphore.h>

#include "pmb.h"

#define PB_DEVICE_NAME "william_pmb"            

//#define DEBUG
#ifdef DEBUG
	#define pr_debug(fmt, arg...) printk(KERN_DEBUG fmt, ##arg)
#else
	#define pr_debug(fmt, arg...) printk(KERN_INFO fmt, ##arg)
#endif
/*
用于调试驱动的一种方法。
其中printk函数中的参数,定义于<linux/kernel.h>。
#define	KERN_EMERG	"<0>"	/* system is unusable紧急事件消息,系统崩溃前提示,表示系统不可用*/
#define	KERN_ALERT	"<1>"	/* action must be taken immediately	报告消息,表示必须马上采取措施*/
#define	KERN_CRIT	"<2>"	/* critical conditions临界情况,通常用于涉及严重的硬件或软件操作失败*/
#define	KERN_ERR	"<3>"	/* error conditions错误情况,驱动程序常用来报告硬件错误*/
#define	KERN_WARNING	"<4>"	/* warning conditions警告,对可能出现问题的情况进行警告*/
#define	KERN_NOTICE	"<5>"	/* normal but significant condition正常但又重要的情况,常用于提醒与安全相关的消息*/
#define	KERN_INFO	"<6>"	/* informational提示信息*/
#define	KERN_DEBUG	"<7>"	/* debug-level messages调试级别消息*/
*/

/*硬件上的一些定义*/
#define PECR_E0IS	(1 << 29)	// EXT_WAKEUP<0> Interrupt Status
#define PECR_E0IE	(1 << 28)	// EXT_WAKEUP<0> Pin Interrupt Enable
#define PECR_DIR0(1 << 4)	//Direction for EXT_WAKEUP<0>: 0/1= input/output
#define PECR_IVE0	(1 << 0)	//Input Value for EXT_WAKEUP<0>

//Currently we have
#define IRQ_WAKEUP0	PXA_IRQ(49)	/* EXT_WAKEUP0 */
#define IRQ_WAKEUP1	PXA_IRQ(50)	/* EXT_WAKEUP1 */

//一个重要的全局结构体
static struct powerkey_t pwk;
/*
struct powerkey_t 
{	
	int ifopen;	    //check if device is opened
	unsigned int pressed;	    //current key state		[0/1=release/press]
	int event;	//event:	    [0/1/2/3=narmal/sleep/deepsleep/wakeup]
	wait_queue_head_t keywaitq;	    //powerkey queue
	struct tast_struct *p_thread;
	int checkforsleep;	    //check if which event for sleep
	int ifhandshake;	    //check if need to handshake with app[]
	int handshake;
	struct fasync_struct *pwrkey_async_queue;
	int ifreleasehandshakecnt;	    //0: you can release handshake. >0: can't release handshake	
};
*/

static int pb_handshake(int sig,int mode);

static int wakeup_init(void)
{	
	PECR |= PECR_E0IE;	//enable wakeup0 interrupt
	PECR &= ~PECR_DIR0;	//as input
	return 0;
}

static int disable_wakeup(void)
{	
	PECR &= ~PECR_E0IE;	//disable wakeup0 interrupt
	return 0;
}

static int wakeup_ack_irq(void)
{	
	PECR |= PECR_E0IS;	//interrupt state, write 1 to clear
	return 0;
}


static int pb_sleep_exe(int sleep)
{
	int ret;
	struct file *fd;
	mm_segment_t old_fs;
	//printk("%s\n",__FUNCTION__);
	
	fd = filp_open("sys/power/state",O_RDWR,0);
	if(IS_ERR(fd))
	{
		printk("Open sys/power/state fail,ret = %ld \n",IS_ERR(fd));
		return -1;
	}
	old_fs = get_fs();
	set_fs(KERNEL_DS);
	switch(sleep)
	{
		case SLEEP_EVENT:
			printk("sleep!\n");
			ret = fd->f_op->write(fd,"mem",3,&fd->f_pos);
			if(ret != 3)
			{
				printk("Write to sleep fail!\n");
			}
			//printk("sleep write ok!\n");
			break;
		case DEEPSLEEP_EVENT:
			ret = fd->f_op->write(fd,"deepsleep",9,&fd->f_pos);
			if(ret != 9)
			{
				printk("Write to deepsleep fail!\n");
			}
			break;
		default:
			break;
	}
	set_fs(old_fs);
	filp_close(fd ,NULL);
	
	return 0;
}
/*
内核文件操作
strcut file* filp_open(const char* filename, int open_mode, int mode);
该函数返回strcut file*结构指针,供后继函数操作使用,该返回值用IS_ERR()来检验其有效性。

操作之前先要定位
void set_fs(mm_segment_t fs);
该函数的作用是改变kernel对内存地址检查的处理方式,其实该函数的参数fs只有两个取值:USER_DS,KERNEL_DS,分别代表用户空间和内核空间
get_fs();    取得当前的设置
off_t sys_lseek(unsigned int fd, off_t offset, unsigned int origin)
offset是偏移量。
若origin是SEEK_SET(0),则将该文件的位移量设置为距文件开始处offset 个字节。
若origin是SEEK_CUR(1),则将该文件的位移量设置为其当前值加offset, offset可为正或负。
若origin是SEEK_END(2),则将该文件的位移量设置为文件长度加offset, offset可为正或负。

ret = fd->f_op->write(fd,"mem",3,&fd->f_pos);    文件读写函数

最后关闭文件 int filp_close(struct file*filp, fl_owner_t id);
*/

static int wait_wakeup_handshake(void)
{
	int ret;
	struct powerkey_t *ppwk = &pwk;

	//printk("%s\n",__FUNCTION__);
	ppwk->event = WAKEUP_EVENT;
	if(ppwk->ifopen > NOOPENED)	
	{
		if(ppwk->ifhandshake == REQUESTHANDSHAKE)	
		{
			if(pb_handshake(SIGIO,POLL_IN))
			{
				printk("Posting Handshake fail\n");
				return -1;
			}
			else
			{
				ppwk->handshake = NONEEDHANDSHAKE;
				ret = wait_event_interruptible_timeout(ppwk->keywaitq, ppwk->handshake == HANDSHAKE_EVENT_OK, (20*HZ));
				if( ret == 0)
				{
					printk("Wake up handshake timeout\n");
				}
			}
		}
		else	
		{
			ppwk->handshake = NONEEDHANDSHAKE;
			ret = wait_event_interruptible_timeout(ppwk->keywaitq, ppwk->handshake == HANDSHAKE_EVENT_OK, (20*HZ));
			if( ret == 0)
			{
				printk("Wake up handshake timeout\n");
			}
		}
	}
	ppwk->handshake = NONEEDHANDSHAKE;
	
	return 0;
}


static irqreturn_t extwakeup_handle(int irq, void *dev_id)
{
	int readreg;
	struct powerkey_t *ppwk = &pwk;
	//printk("%s\n",__FUNCTION__);
	
	wakeup_ack_irq();
	
	readreg = PECR;		
	if(readreg & PECR_IVE0)
	{
		//printk("key is pressed !\n");
		ppwk->checkforsleep = NEEDCHECKSLEEP;
		ppwk->pressed = KEYPRESSED;	
	}
	else
	{
		//printk("key released!\n");
		ppwk->pressed = KEYRELEASE;
	}
	wake_up_interruptible(&ppwk->keywaitq);
	wakeup_ack_irq();
	return IRQ_HANDLED;
}


static int pb_irq_init(void)
{
	int err;
	//printk("%s\n",__FUNCTION__);

	err = request_irq(IRQ_WAKEUP0, &extwakeup_handle,NULL,
				  "ext_wakeup0_detect", NULL);
	
	return err;
}

static int preevent_sleep(const int sleepevent)
{
	int ret;
	struct powerkey_t *p = &pwk;
	
	if(sleepevent == DEEPSLEEP_EVENT)
	{
		if(p->ifopen > NOOPENED)	
			ret = wait_event_interruptible_timeout(p->keywaitq, p->pressed == KEYRELEASE, (3*HZ));
		disable_wakeup();
		wakeup_ack_irq();		
	}


	if(p->ifopen > NOOPENED)
	{
		p->handshake = NONEEDHANDSHAKE;
		ret = wait_event_interruptible_timeout(p->keywaitq, p->handshake == HANDSHAKE_EVENT_OK, (20*HZ));
		if( ret == 0)
		{
			printk("Handshake timeout\n");
		}
		p->handshake = NONEEDHANDSHAKE;
	}

	if(sleepevent == DEEPSLEEP_EVENT)
        {
		wakeup_init();
        wakeup_ack_irq();	
	}

	pb_sleep_exe(sleepevent);
	wait_wakeup_handshake();


	return 0;
}


static int wait_handshake_sleep(int sleepevent)
{
	int ret;
	struct powerkey_t *p = &pwk;
	
	p->event = sleepevent;
	if(p->ifhandshake == REQUESTHANDSHAKE)
	{	//for signal mode
		if(pb_handshake(SIGIO,POLL_IN))
		{
			printk("Posting Handshake fail\n");
			return -1;	
		}
		ret = preevent_sleep(sleepevent);
	}
	else	//for polling mode
	{
		ret = preevent_sleep(sleepevent);
	}
	
	return 0;
}
/*
wait_event_interruptible_timeout(queue, condition, timeout)
使用例如:
(1)初始化等待队列
int flags = 0;
wait_queue_head_t    select_wait;
init_waitqueue_head(&select_wait);
(2)等待事件的发生(条件满足)
{
...
    wait_event_interruptible_timeout(select_wait, flags != 0, HZ/10);
...
}
(3)唤醒等待队列
{
...
   if(waitqueue_active(&select_wait))
   {
       flags = 1;
       wake_up_interruptible( &nd->select_in_wait );
   }
...
}    
*/

static int driver_data_init(void)
{
	struct powerkey_t *ppwk = &pwk;
	
	if(PECR & PECR_IVE0)
	{
		ppwk->pressed = KEYPRESSED;
	}
	else
	{
		ppwk->pressed = KEYRELEASE;
	}
	ppwk->ifopen = NOOPENED;
	ppwk->event = NORMAL_EVENT;
	ppwk->handshake = NONEEDHANDSHAKE;
	ppwk->checkforsleep = NONEEDCHECKSLEEP;
	ppwk->ifhandshake = FREEHANDSHAKE;
	ppwk->ifreleasehandshakecnt = 0;
	init_waitqueue_head(&ppwk->keywaitq);
	return 0; 
}

static int powerkey_thread(void *data)
{
	struct powerkey_t *ppwk = &pwk;
	long ret;
//	unsigned long flags;

	//printk("%s\n",__FUNCTION__);
	
	while(!kthread_should_stop())
	{
		set_current_state(TASK_INTERRUPTIBLE);
		//if(kthread_should_stop()) break;
		while(ppwk->checkforsleep == NEEDCHECKSLEEP)
		{
			ret = wait_event_interruptible_timeout(ppwk->keywaitq, ppwk->pressed == KEYRELEASE, (2*HZ));
			if(ret == 0)
			{	//time out
				//disable_wakeup0_int();
				//wakeup0_ack_irq();
				ret = wait_handshake_sleep(DEEPSLEEP_EVENT);
				ppwk->checkforsleep = NONEEDCHECKSLEEP;
				//wakeup0_init();
				//wakeup0_ack_irq();
			}
			else
			{
				disable_wakeup();
				wakeup_ack_irq();
				ret = wait_handshake_sleep(SLEEP_EVENT);
				ppwk->checkforsleep = NONEEDCHECKSLEEP;
				wakeup_init();
				wakeup_ack_irq();
			}
		}
		
		//schedule_timeout();
		ret = wait_event_interruptible_timeout(ppwk->keywaitq, ppwk->checkforsleep == NEEDCHECKSLEEP, (2*HZ));
	}
	printk("%s exit!\n",__FUNCTION__);
	return 0;
}


static int create_powerkey_thread(void)
{
	struct powerkey_t *ppwk = &pwk;
	//printk("%s\n",__FUNCTION__);
	ppwk->p_thread = kthread_run(&powerkey_thread,NULL,"powerkey_thread");
	if(ppwk->p_thread == NULL)
	{
		printk("%s failed\n",__FUNCTION__);
		return -1;
	}
	return 0;
}

static void delete_powerkey_thread(void)
{
	struct powerkey_t *ppwk = &pwk;
	//printk("%s\n",__FUNCTION__);
	if(ppwk->p_thread)
	{
		kthread_stop(ppwk->p_thread);
	}
	ppwk->p_thread = NULL;
}
/*
struct task_struct *kthread_create(int (*threadfn)(void *data),void *data,const char *namefmt, ...);
线程创建后,不会马上运行,而是需要将kthread_create() 返回的task_struct指针传给wake_up_process(),然后通过此函数运行线程。
struct task_struct *kthread_run(int (*threadfn)(void *data),void *data,const char *namefmt, ...);    创建并启动线程
int kthread_stop(struct task_struct *thread);
线程一旦启动起来后,会一直运行,除非该线程主动调用do_exit函数,或者其他的进程调用kthread_stop函数,结束线程的运行
kthread_should_stop()函数,我们需要在开启的线程中嵌入该函数并检查此函数的返回值,否则kthread_stop是不起作用的
类似,我们也可以创建其他几条线程。
*/

static int pb_fasync(int fd,struct file* filp,int mode)
{
	struct powerkey_t *ppwk = &pwk;
	//printk("%s\n",__FUNCTION__);
	ppwk->ifhandshake = REQUESTHANDSHAKE;
	ppwk->ifreleasehandshakecnt++;
	return fasync_helper(fd,filp,mode,&ppwk->pwrkey_async_queue);
}


static int pb_handshake(int sig,int mode)
{
	struct powerkey_t *ppwk = &pwk;
	//printk("%s\n",__FUNCTION__);
	if(ppwk->pwrkey_async_queue)
	{
		kill_fasync(&ppwk->pwrkey_async_queue,sig,mode);
		return 0;
	}
	//printk("%s failed.no async_queue\n",__FUNCTION__);
	return -1;
}


static void release_fasync(struct file *file)
{
	int ret;
	struct powerkey_t *p = &pwk;
	//printk("%s\n",__FUNCTION__);
	
	if(p->ifreleasehandshakecnt != 0)
		p->ifreleasehandshakecnt--;

	if(p->ifhandshake == REQUESTHANDSHAKE)	//have
	{
		if(p->ifreleasehandshakecnt == 0)
		{
			ret = fasync_helper(-1,file,0,&p->pwrkey_async_queue);
			printk("%s fasync_helper ret=%d\n",__FUNCTION__,ret);
			if(p->pwrkey_async_queue)
			{
				p->pwrkey_async_queue = NULL;
			}
			p->ifhandshake = FREEHANDSHAKE;
		}
	}
}
/*
异步通知:一旦设备就绪,则主动通知应用程序,不需要查询。
设备驱动中异步通知比较简单,主要用到fasync_struct结构体,还有下面两个函数。
int fasync_helper(int fd, struct file *filp, int mode, struct fasync_struct **fa);   处理FASYNC标志变更的函数。记得要释放
void kill_fasync(struct fasync_struct **fa, int sig, int band);    释放信号用的函数
sig信号用的最多的是SIGIO,可读时band设置为POLL_IN,可写时band设置为POLL_OUT。
*/

int pb_open(struct inode *inode,struct file *filp)
{
	struct powerkey_t *p = &pwk;
	p->ifopen++;
	return 0;
}

int pb_release(struct inode *inode,struct file *filp)
{
	struct powerkey_t *p = &pwk;
	
	if(p->ifopen > NOOPENED)
		p->ifopen--;
	release_fasync(filp);    //处理FASYNC标志
	return 0;
}
/*
static ssize_t pb_read(struct file *filp,char *buf,size_t size,loff_t *ppos)    
{	
	return 0;
}

static ssize_t pb_write(struct file *filp,const char *buf,size_t size,loff_t *ppos)
{
	return 0;
}
*/
static int pb_ioctl(struct inode *inodep,struct file *filp,unsigned int cmd,unsigned long arg)
{
	int ret = 0;
	struct powerkey_t *ppwk = &pwk;

	switch(cmd)
		{
			case HANDSHAKE_EVENT_OK:
				ppwk->handshake = HANDSHAKE_EVENT_OK;
				wake_up_interruptible(&ppwk->keywaitq);
				break;
			case GET_EVENT_STATE:
				if(copy_to_user((int *)arg,&ppwk->event,sizeof(ppwk->event)))
					{
						ret = -1;
					}
				else
					{
						ppwk->event = NORMAL_EVENT;
					}
				break;
			case FREE_HANDSHAKE_EVENT:
				release_fasync(filp);
				break;
			//case SUSPEND_EVENT:
				//break;
			default:
				break;
		}

	return 0;
}

static struct file_operations pb_fops = {
	.owner = THIS_MODULE,
	//.read = pb_read,
	//.write = pb_write,
	.ioctl = pb_ioctl,
	.open = pb_open,        
	.release = pb_release,
	.fasync	= pb_fasync,
};

static struct miscdevice pb_miscdev = {
	.minor = 100,    
	.name = PB_DEVICE_NAME,
	.fops = &pb_fops,
};    

static int pb_probe(struct platform_device *pdev)
{
	int ret;
	
	ret = misc_register(&pb_miscdev);

	wakeup_init();
	wakeup_ack_irq();
	ret = pb_irq_init();    //中断初始化
	if(ret)
		{
			misc_deregister(&pb_miscdev);
		}
	driver_data_init();
	ret = create_powerkey_thread();    //创建内核线程
	if(ret)
		{
			free_irq(IRQ_WAKEUP0,NULL);
			misc_deregister(&pb_miscdev);
		}
	
	return 0;
}

static int pb_remove(struct platform_device *pdev)
{
	int ret;
	struct powerkey_t *ppwk = &pwk;
	ret = wakeup_ack_irq();
	wake_up_interruptible(&ppwk->keywaitq);    //唤醒等待队列
	delete_powerkey_thread();    //干掉内核线程
	free_irq(IRQ_WAKEUP0, NULL);    //干掉中断
	
	misc_deregister(&pb_miscdev);
	return 0;
}

#ifdef CONFIG_PM       
static int pb_suspend(struct platform_device *pdev, pm_message_t state)
{	
	wakeup_ack_irq();
	disable_wakeup();
	free_irq(IRQ_WAKEUP0, NULL);
	return 0;
}

static int pb_resume(struct platform_device *pdev)
{
	pb_irq_init();
	return 0;
}
#else
#define pb_suspend NULL
#define pb_resume NULL
#endif

static struct platform_driver pb_driver = {
	.driver = {
		.name = "william_pmb",	    	
		},
	.probe = pb_probe,
	.remove = pb_remove,
	.suspend = pb_suspend,
	.resume = pb_resume,
};

static int __init pb_init(void)
{
	printk("hello pb !\n");
	return platform_driver_register(&pb_driver);
}

static void __exit pb_exit(void)
{
	platform_driver_unregister(&pb_driver);
	printk("bye pb !\n");
}

module_init(pb_init);  
module_exit(pb_exit); 

MODULE_AUTHOR("William Wang");  
MODULE_LICENSE("GPL");




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值