key_drive.c
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/poll.h>
#include <linux/irq.h>
#include <asm/irq.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/io.h>
#include <linux/interrupt.h>
#include <asm/uaccess.h>
#include <linux/gpio.h>
#include <mach/regs-gpio.h>
#include <mach/hardware.h>
#include <linux/platform_device.h>
#include <linux/cdev.h>
#include <linux/miscdevice.h>
int key_major=0;
int key_minor=0;
struct cdev keycdev;
struct class *key_class;
static struct device *key_device;
#define KEYCLASSNAME "mykeyclass"
const char keydevname[]={"mykey"};
#define GPH2CON 0xE0200C40
#define GPH2DAT 0xE0200C44
int *gph2con;
int *gph2dat;
struct key_struct{
unsigned int irq;
unsigned int pin;
unsigned char value;
const char *name;
};
//DECLARE_WAIT_QUEUE_HEAD定義並初始化一個等待隊列頭:
//相當於wait_queue_head_t my_key_waitq;加 init_waitqueue_head ( &my_key_waitq )
DECLARE_WAIT_QUEUE_HEAD(my_key_waitq);
static unsigned char pressed = 0;
static unsigned char key_value = 0;
struct fasync_struct * fasync_queue;
struct key_struct key_desc[]={
{IRQ_EINT(16),S5PV210_GPH2(0),0x01,"key1"},
{IRQ_EINT(17),S5PV210_GPH2(1),0x02,"key2"},
{IRQ_EINT(18),S5PV210_GPH2(2),0x04,"key3"},
{IRQ_EINT(19),S5PV210_GPH2(3),0x08,"key4"},
};
void keyioremap(void)
{
gph2con=ioremap(GPH2CON, 4); //IO映射:將物理地址GPH2CON轉爲虛擬地址gph2con,轉換長度4個字節
gph2dat=ioremap(GPH2DAT, 4);
}
void keyunremap(void)
{
iounmap(gph2con); //解除虛擬地址爲gph2con的IO映射 與 ioremap相反
iounmap(gph2dat);
}
int mykeyfasync (int fd, struct file *fops, int onoff)
{
/*
註冊fasync
struct fasync_struct {
spinlock_t fa_lock;
int magic;
int fa_fd;
struct fasync_struct *fa_next; //singly linked list
struct file *fa_file;
struct rcu_head fa_rcu;
};
fasync_helper初始化一個fasync_struct結構體,該結構體描述了將要發送信號的進程 PID (fasync_struct->fa_file->f_owner->pid)
*/
printk(KERN_INFO "mykeyfasync...\n");
return fasync_helper( fd, fops, onoff, &fasync_queue);
}
irqreturn_t handler(int irq, void *dev_id)
{
volatile struct key_struct key_dr = *(volatile struct key_struct*)dev_id;//通dev_id傳參
int value;
value=gpio_get_value(key_dr.pin); //得到該pin電平
if(value==0)
{
key_value = key_dr.value ;
pressed = 1;
}else
pressed=0;
printk(KERN_INFO "irq:%d,gpio_get_value:%d; key_dr.value:%d key_dr.name:%s\n",irq,value,key_dr.value,key_dr.name);
wake_up_interruptible(&my_key_waitq);//pressed = 1;且調用wake_up_interruptibl才能喚醒等待隊列
/*
發送信號SIGIO信號給fasync_struct 結構體所描述的PID,觸發應用程序的SIGIO信號處理函數
*/
kill_fasync(&fasync_queue, SIGIO, POLLIN | POLLRDNORM);
return 0;
}
/*
POLLIN
有數據可讀。
POLLRDNORM
有普通數據可讀。
POLLRDBAND
有優先數據可讀。
POLLPRI
有緊迫數據可讀。
POLLOUT
寫數據不會導致阻塞。
POLLWRNORM
寫普通數據不會導致阻塞。
POLLWRBAND
寫優先數據不會導致阻塞。
POLLMSG
POLLER
指定的文件描述符發生錯誤。
POLLHUP
指定的文件描述符掛起事件。
POLLNVAL
指定的文件描述符非法。
*/
void keyinit(void)
{
keyioremap();
writel(0xffffffff,gph2con); //寫32bit到gph2con
}
void keyrequestirq(void)
{
int i=0,ret;
for(i=0;i<(sizeof(key_desc)/sizeof(struct key_struct));i++)
{
printk(KERN_INFO "irq:%d name: %s \n",key_desc[i].irq, key_desc[i].name);
/*
//註冊中斷,中斷共享一個處理函數
* @handler: interrupt handler function 當發送中斷時的回調函數
* @flags: flags (see IRQF_* above) 這裏爲下降沿觸發中斷 //Z:\kernel\linux-3.0.8\include\linux\irq.h
* @name: name of the device 設備名
* @dev_id: cookie to identify the device 一般傳入該設備自定義得結構體,通過該結構體可以區分是哪個中斷產生的,也可以通過中斷號來區分
* @irq: interrupt number 中斷號
*/
ret=request_irq(key_desc[i].irq, handler,IRQ_TYPE_EDGE_FALLING, key_desc[i].name, &key_desc[i]);
if(ret) {
printk(KERN_INFO "ret is %d\n", ret);
break;
}
}
}
void keyfreeirq(void)
{
int i=0;
for(i=0;i<(sizeof(key_desc)/sizeof(struct key_struct));i++)
{
free_irq(key_desc[i].irq, &key_desc[i]); //註銷中斷
}
}
int keyopen (struct inode *inode, struct file *fops)
{
keyrequestirq();
return 0;
}
int keyrelease (struct inode *inode, struct file *fops)
{
keyfreeirq();
return 0;
}
ssize_t keyread (struct file *fops, char __user *buf, size_t n, loff_t *offer)
{
int ret;
if(n != 1) { //一個參數
printk(KERN_EMERG"The driver can only give one key value once!\n");
return -EINVAL;
}
if(fops->f_flags & O_NONBLOCK) {
if(!pressed)
return -EBUSY;
} else {
wait_event_interruptible(my_key_waitq, pressed);/*pressed爲1時繼續執行後面,否則休眠,休眠時可以中斷*/
pressed = 0;
ret = copy_to_user(buf, &key_value, 1); //將內核空間數據複製到用戶空間
printk(KERN_INFO "key: %d\n",key_value);
if(ret) {
printk(KERN_EMERG"key copy_to_user error\n");
return -ENOMEM;
}
}
return 0;
}
static unsigned int keypoll(struct file *filep, poll_table *wait)
{
unsigned int mask = 0;
poll_wait(filep, &my_key_waitq, wait);////將my_key_waitq等待隊列添加到poll_table *wait表中管理
if(pressed) {
mask |= POLLIN | POLLRDNORM; //有值可讀
}
printk(KERN_INFO "keypoll..\n");
return mask;
}
const struct file_operations keyfops={
.owner = THIS_MODULE,
.open=keyopen,
.release=keyrelease,
.read=keyread,
.fasync=mykeyfasync, //異步通知,即驅動設備通知應用程序
.poll=keypoll,
};
static int __init keydriveinit(void)
{
int ret;
dev_t devnum=MKDEV(key_major,key_minor);
cdev_init(&keycdev,&keyfops); //初始化字符設備
if(key_major)
{
ret=register_chrdev_region(devnum, 1, keydevname); //靜態註冊設備號
}else
{
ret=alloc_chrdev_region(&devnum, 0, 1, keydevname);//動態申請設備號,由內核分配
}
if(ret)
{
printk(KERN_INFO "register char dev failure...\n");
goto myregister_error;
}
key_major=MAJOR(devnum);
key_minor=MINOR(devnum);
ret=cdev_add(&keycdev, devnum, 1); //將字符設備添加到內核
if(ret)
{
printk(KERN_INFO "cdev_add failure...\n");
goto mycdev_add_error;
}
key_class= class_create(THIS_MODULE, KEYCLASSNAME);//創建一個class 將在/sys/class/下產生一個名爲KEYCLASSNAME的類
if(IS_ERR(key_class)) { //覈查該類是否創建成功
printk(KERN_INFO "class_create failure %ld\n",PTR_ERR(key_class));
goto myclass_error;
}
key_device= device_create(key_class, NULL, devnum, NULL, KEYCLASSNAME); //創建設備文件節點
if(IS_ERR(key_device)) {
printk(KERN_INFO "device_create failure %ld\n",PTR_ERR(key_device));
goto mydevice_error;
}
keyinit(); //初始化硬件
printk("key drive init ok. major:%d,minor:%d,drive name:%s\n",key_major,key_minor,keydevname);
return 0;
mydevice_error:
device_destroy(key_class,devnum);//銷燬字符設備節點
myclass_error:
class_destroy(key_class);//銷燬類
mycdev_add_error:
cdev_del(&keycdev);//從內核中移除該字符設備
myregister_error:
unregister_chrdev_region(devnum, 1);//註銷設備號
return ret;
}
static void __exit keydriveexit(void)
{
keyunremap();
device_destroy(key_class,MKDEV(key_major, key_minor));
class_destroy(key_class);
cdev_del(&keycdev);
unregister_chrdev_region(MKDEV(key_major, key_minor), 1);
printk(KERN_INFO "keydriveexit...\n");
}
module_init(keydriveinit);
module_exit(keydriveexit);
MODULE_AUTHOR("jump");
MODULE_LICENSE("Dual BSD/GPL");
key_user.c 測試程序
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <poll.h>
#include <signal.h>
#include <asm-generic/errno-base.h>
#if 1 //異步通知fasync中斷測試
int fd;
void signfunc(void)
{
unsigned char value;
int rec=read(fd,(void *)&value,1); // 在通知函數讀取哪個按鍵按下
printf("signfunc...rec:%d value:%d \n",rec,value);
}
int main()
{
int oflags;
fd = open("/dev/mykeyclass", O_RDWR);
if(fd < 0) {
printf("/dev/mykeyclass open error\n");
return -EBUSY;
}
signal(SIGIO, (void *)&signfunc); //設置驅動異步通知的處理函數爲signfunc,又驅動調用kill_fasync來通知應用程序
fcntl(fd, F_SETOWN, getpid());//設置將要在文件描述符fd上接收信號SIGIO的進程爲本應用程序id,即異步通知的對象爲本應用程序
oflags = fcntl(fd, F_GETFL);//讀取文件狀態標誌
fcntl(fd, F_SETFL, oflags | FASYNC);//設置文件狀態標誌
while(1) {
//sleep(2);
}
return 0;
}
#else
//中斷poll測試程序:
int main()
{
int fd;
int ret;
unsigned char key_value;
struct pollfd fds[1];
fd = open("/dev/mykeyclass", O_RDWR);
if(fd < 0) {
perror("/dev/mykeyclass open error\n");
return 0;
}
fds[0].fd = fd;
fds[0].events = POLLIN;
while(1) {
/*
struct pollfd {
int fd; //文件描述符
short events; // 等待的事件
short revents; // 實際發生了的事件
} ;
int poll(struct pollfd fd[], nfds_t nfds, int timeout);
第二個參數nfds:要監視的描述符的數目
第三個參數timeout:是一個用毫秒錶示的時間,是指定poll在返回前沒有接收事件時應該等待的時間。如果 它的值爲-1,poll就永遠都不會超時
*/
ret = poll(fds, 1, 3000);
printf("ret = %d\n", ret);
if(ret == 0) {
printf("timeout...\n");
}else {
read(fd, &key_value, 1);
printf("key_value=0x%x\n", key_value);
}
}
return 0;
}
#endif