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