歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> 在Linux下的中斷方式讀取按鍵驅動程序

在Linux下的中斷方式讀取按鍵驅動程序

日期:2017/3/1 9:08:22   编辑:Linux編程

// 在Linux下的中斷方式讀取按鍵驅動程序
//包含外部中斷 休眠 加入poll機制
// 采用異步通知的方式
// 驅動程序發 ---> app接收 (通過kill_fasync()發送)
// 為了使設備支持異步通知機制,驅動程序中涉及以下3項工作:
// 1. 支持F_SETOWN命令,能在這個控制命令處理中設置filp->f_owner為對應進程ID。
// 不過此項工作已由內核完成,設備驅動無須處理。
// 2. 支持F_SETFL命令的處理,每當FASYNC標志改變時,驅動程序中的fasync()函數將得以執行。
// 驅動中應該實現fasync()函數。
// 3. 在設備資源可獲得時,調用kill_fasync()函數激發相應的信號
// 應用程序:
// fcntl(fd, F_SETOWN, getpid()); // 告訴內核,發給誰
// Oflags = fcntl(fd, F_GETFL);
// fcntl(fd, F_SETFL, Oflags | FASYNC); // 改變fasync標記,最終會調用到驅動的faync > fasync_helper:初始化/釋放fasync_struct
// 外部中斷測試程序 包含poll機制 進程之間異步通信 加入原子操作
// 原子操作:指的是在執行過程中不會被別的代碼路徑所中斷的操作。
// 信號量的實現
// 阻塞 :是指在執行設備操作時若不能獲得資源則掛起進程,直到滿足可操作的條件後再進行操作。
// 被掛起的進程進入休眠狀態,被從調度器的運行隊列移走,直到等待的條件被滿足。
// 非阻塞:進程在不能進行設備操作時並不掛起,它或者放棄,或者不停地查詢,直至可以進行操作為止。
// 加入定時器消抖動功能

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

#define usingatomic (0) // 0使用信號量 1使用的是原子操作

//設備類

static struct class *Eint_class;

// 設備節點

static struct class_device *Eint_class_devs;

// 地址映射

volatile unsigned long *gpfcon;

volatile unsigned long *gpfdat;

volatile unsigned long *gpgcon;

volatile unsigned long *gpgdat;

// 全局變量 存放中斷讀出的鍵值

static unsigned int key_val;

//創建一個休眠隊列

static DECLARE_WAIT_QUEUE_HEAD(button_waitq);

/* 中斷事件標志, 中斷服務程序將它置1,third_drv_read將它清0 */

static volatile int ev_press = 0;

//信號量初始化結構體

static struct fasync_struct *button_async_queue;

// 定時器結構體

struct timer_list buttons_timer;

// 存儲外部中斷號和鍵值結構體變量

static struct pin_desc *irq_pd;

//定義結構體 存放按鍵 pin 端口 key_val鍵值

struct pin_desc

{

unsigned int pin;

unsigned int key_val;

};

#if usingatomic

//定義原子變量v並初始化為1

atomic_t canopen = ATOMIC_INIT(1);

#else

//定義互斥鎖 信號量

static DECLARE_MUTEX(button_lock);

#endif

//定義結構體數組 存放中斷端口和鍵值

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 *ignored)

{

irq_pd = ( struct pin_desc *)ignored;

mod_timer(&buttons_timer, jiffies+HZ/100); //10ms 產生中斷

// return IRQ_RETVAL(IRQ_HANDLED);

return IRQ_HANDLED;

}

//定時器中斷函數

static void buttons_timer_function(unsigned long data)

{

struct pin_desc *pins_desc= irq_pd;

unsigned int pinval;

if(!pins_desc)

return;

pinval=s3c2410_gpio_getpin(pins_desc->pin); //讀取IO的值

if(pinval)

{

key_val =0x80|pins_desc->key_val;

}

else

{

key_val =pins_desc->key_val;

}

ev_press = 1; /* 表示中斷發生了 */

wake_up_interruptible(&button_waitq); /* 喚醒休眠的進程 */

kill_fasync (&button_async_queue, SIGIO, POLL_IN);//發送信號給app

}

//打開設備調用

//初始化IO端口 配置為輸入模式

//GPF0-->S2 GPF2-->S3 GPG3-->S4 GPG11-->S5

static int Eint_drv_open(struct inode *inode, struct file *file)

{

// *gpfcon &=~((3<<2*0)|(3<<2*2));

// *gpgcon &=~((3<<3*2)|(3<<11*2));

#if usingatomic

if(!atomic_dec_and_test(&canopen))// 原子操作

{

atomic_inc(&canopen);//自加1

printk("this a user in the use of\n");

return -EBUSY;//返回忙

}

#else

if (file->f_flags & O_NONBLOCK)

{

//非阻塞 立馬返回

if (down_trylock(&button_lock))

return -EBUSY;

}

else

{

down(&button_lock);

}

#endif

printk("Eint_drv_open successed!\n");

request_irq(IRQ_EINT0,buttons_irq, IRQT_BOTHEDGE, "s2", &pins_desc[0]);//EINT0邊沿觸發方式

request_irq(IRQ_EINT2,buttons_irq, IRQT_BOTHEDGE, "s3", &pins_desc[1]);//EINT2邊沿觸發方式

request_irq(IRQ_EINT11,buttons_irq, IRQT_BOTHEDGE, "s4", &pins_desc[2]);//EINT11邊沿觸發方式

request_irq(IRQ_EINT19,buttons_irq, IRQT_BOTHEDGE, "s5", &pins_desc[3]);//EINT19邊沿觸發方式

return 0;

}

//write時候調用

static ssize_t Eint_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)

{

}

//read時候調用

//讀取按鍵值

ssize_t Eint_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 -EAGAIN;

}

else

{

//如果沒有按鍵按下 則休眠

wait_event_interruptible(button_waitq,ev_press);

}

//如果有按鍵動作發生的話 則返回

copy_to_user(buf, &key_val, 1);

ev_press = 0;//清中斷標志位

// printk("key_val = 0x%x\n", key_val);

}

//關閉驅動時候調用

static int Eint_drv_colse(struct inode *inode, struct file *file)

{

#if usingatomic

atomic_inc(&canopen);//自加1

#else

up(&button_lock);

#endif

free_irq(IRQ_EINT0, &pins_desc[0]);//EINT0釋放中斷

free_irq(IRQ_EINT2, &pins_desc[1]);//EINT2釋放中斷

free_irq(IRQ_EINT11, &pins_desc[2]);//EINT11釋放中斷

free_irq(IRQ_EINT19, &pins_desc[3]);//EINT19釋放中斷

printk("Eint_drv_colse successed!\n");

}

//poll時候調用

// 在規定時間內沒有按下按鍵 就返回超時

//中斷沒有發生 就return 0,在do_sys_poll中就會讓系統休眠,喚醒休眠是chedule_timeout(__timeou)超時

//中斷發生 return POLLIN | POLLRDNORM,在do_sys_poll退出休眠,喚醒進程

static unsigned int Eint_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;

}

//在應用程序中使用fcnt() 時候調用

static int Eint_drvl_fasync (int fd, struct file *filp, int on)

{

printk("\ndrivec:signal_fasync successed !\n");

return fasync_helper (fd, filp, on, &button_async_queue);

}

//告訴內核

static struct file_operations Eint_drv_fops = {

.owner = THIS_MODULE, // 這是一個宏,推向編譯模塊時自動創建的__this_module變量

.open = Eint_drv_open,

.write = Eint_drv_write,

.read = Eint_drv_read,

.release= Eint_drv_colse,

.poll = Eint_drv_poll,

.fasync = Eint_drvl_fasync,

};

int major;//自動分配主設備號

//安裝驅動的時候調用

//注冊驅動 創建設備類 創建設備節點 創建虛擬地址 創建定時器任務

int Eint_drv_init(void)

{

// 創建一個定時器

init_timer(&buttons_timer);

buttons_timer.function = buttons_timer_function;

add_timer(&buttons_timer);

major=register_chrdev( 0, "key_drv",&Eint_drv_fops);//告訴內核 注冊驅動

Eint_class = class_create(THIS_MODULE, "key_drv");//獲取一個設備信息類

if (IS_ERR(Eint_class))

return PTR_ERR(Eint_class);

Eint_class_devs = class_device_create(Eint_class, NULL, MKDEV(major, 0), NULL, "buttons");

if (unlikely(IS_ERR(Eint_class_devs)))

return PTR_ERR(Eint_class_devs);

//轉換虛擬地址

gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);

gpfdat = gpfcon+1;

gpgcon =(volatile unsigned long *)ioremap(0x56000060,16);

gpgdat =gpgcon+1;

printk("Eint_drv_init successed!\n");

return 0;

}

//卸載驅動程序的時候調用

//卸載驅動 刪除設備 刪除設備節點 刪除地址映射

void Eint_drv_exit(void)

{

unregister_chrdev( major, "key_drv");//卸載驅動

class_device_unregister(Eint_class_devs);//刪除設備

class_destroy(Eint_class);//刪除設備節點

iounmap(gpgcon);//刪除地址映射

iounmap(gpfcon);

printk("\nEint_drv_exit successed!\n");

}

module_init(Eint_drv_init);

module_exit(Eint_drv_exit);

MODULE_LICENSE("GPL");

Copyright © Linux教程網 All Rights Reserved