這是一個關於休眠和關機的按鍵驅動。板子:pxa31X系列 內核:2.6.25 這個驅動用到了內核文件操作,內核線程,等待隊列,異步通知,並介紹了一種調試驅動的方法。 [cpp] #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) { www.2cto.com platform_driver_unregister(&pb_driver); printk("bye pb !\n"); } module_init(pb_init); module_exit(pb_exit); MODULE_AUTHOR("William Wang"); MODULE_LICENSE("GPL");