// 在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");