Linux键盘驱动范例

键盘在所有的驱动之中最为简单的一种,但它却包含了驱动的基本框架,对以后继续深入学习其他复杂的驱动大有裨益,以下便为你逐步剖析驱动的开发。采用的是查询方式。转载请注明出处:

一.内核模块的注册和撤销
在加载模块的时候,首先运行的是内核模块的注册函数。它的功能包括内核注册设备以及变量的初始化。

static int head,tail;
int  _init Keypad_init(void)
{
int result;
result=register_chrdev(KEY_LED_MAJOR,KEY_LED_NAME,&Keypad_fops);
Keypad_clear();
init_waitqueue_head(&queue);
prink("%s %s initialized.\n",KEY_LED_NAME,KEY_LED_VERSION);//不能用prinf
return 0;
}
module_init(Keypad_init);//加载模块
void _exit Keypad_cleanup(void)
{
del_timer(&timer);
unregister_chrdev(KEY_LED_MAJOR,KEY_LED_NAME);
prink("Keypad driver removed \n");
}
module_exit(Keypad_cleanup);//卸载该模块

二.虚拟文件系统与硬件驱动的接口
static struct file_operations Keypad_fops={
open:Keypad_open,
read:Keypad_read,
poll:Keypad_poll,
fasync:Keypad_fasync,
release:Keypad_release,
};
该接口定义完之后一些便是对这几个具体函数的实现了!现在我们一起进入下一步吧,是不是觉得其实没什么难度的呢?别那么早开心着呢?这几个函数的实现时候,涉及到很多技术,包括内核定时器,*等待队列的具体实现(阻塞方式),异步方式的具体实现技巧,循环队列。看到这么多技术你是否感到很兴奋呢?以下本人将以通俗的方式为你讲解,希望你能理解。

三.设备的打开操作接口函数具体实现(Keypad_open)
设备打开一般包括两大操作,一是完成设备的初始化,二是设备引用计数器加1
static int Keypad_open(struct inode *inode,struct file *filp)
{
read_xy();
try_module_get(THIS_MODULE);//此函数为Linux 2.6内核增加的,不同于2.4内核,功能是计数器的值加1
return 0;
}
static void read_xy(void)
{
new_data();//获取键值函数
keypad_starttimer();//开启内核定时器,在固定周期时间内获取键盘新的变化
}
以下实现键盘键值获取函数read_xy()
主要是从KEY_CS(对应的读入地址,之前可以根据具体的硬件设备定义,比如#define kEY_CS(*(volatile unsigned short *)(0xf820000))此处应该根据具体的不同而不同!
将读入的键值存入buf[]缓存中,环形缓冲的写指针是head,读指针是tail,前面已经定义过了
////////////////////////////////键盘事件的数据结构定义/////////////////////////////////
typedef struct{
ulong status;//按键的值
ulong click;//是否有按键按下,1表示有,0表示没有
}KEY_EVENT
static KEY_EVENT cur_data,buf[BUFSIZE];//BUFSIZE为宏定义,用于定义环形缓冲的大小
static void new_data(void)
{
if((KEY_CS & 0xff)!=0xff)  //从KEY_CS地址读入数据,若有一个为0则表示有一个按键被按下了(此处硬件电路为低电平有效)
{
switch(KEY_CS & 0xff){
case ~KEY0 & 0xff:
cur_data.status=1;///////1被按下
break;
case ~KEY1 & 0xff:
cur_data.status=2;//2被按下
break;
/////////其他一样添加,懂吗??
}
cur_data.click=1;
}
else if(KEY_CS & 0xff==0xff){
cur_data.click=0;
cur_data.status=0;
}
if(head!=tail){////////循环队列缓冲区的应用在此开始了^_^
int last=head--;
if(last<0)////////若已经到了对首之前,则跳到队尾,以实现循环队列
last=BUFSIZE-1;
}
//////按键信息存入循环队列缓冲区中
buf[head]=cur_data;
if(++head==BUFSIZE)
head=0;
if(head==tail && tail++=BUFSIZE)
tail=0;
if(fasync)
kill_fasync(&fasyc,SIGIO,POLL_IN);
wake_up_interruptible(&queue);
}


接下来我们介绍其他几个文件接口函数的实现

四.先介绍关闭函数keypad_release(),为什么先介绍它呢?道理很简单,应该它比较简单,先让大家做下热身运动,在介绍完这个之后,继续会介绍一个比较复杂的函数,看你吃得消没有哦
关闭操作主要实现的是:关闭设备异步通知,设备计数器减1,删除定时器信号中断
static int Keypad_release(struct inode *inode,struct)
{
Keypad_fasync(-1,filp,0);
module_put(THIS_MODULE);
del_timer(&timer);
return 0;
}

五.设备读取操作接口函数实现Keypad_read()
主要作用是从缓冲区读取键值,通过调用get_data()实现,通过copy_to_user()函数将键值复制到用户的数据区中
static ssize_t Keypad_read(struct file *filp,char *buf,ssize_t count,loff_t *l)
{
DECLEARE_WAITQUEUE(wait,current);//声明等待队列,将当前进程加入到等待队列中
KEY_EVENT t;
ulong out_buf[2];
if(head==tail)//当前循环队列中没有数据可以读取
{
if(filp->f_flags & O_NONBLOCK)//假如用户采用的是非堵塞方式读取
return _EAGAIN;
add_wait_queue(&queue,&wait);//将当前进程加入等待队列
current->state=TASK_INTERRUPTIBLE;//设置当前进程的状态
while((head==tail)&&!signal_pending(current))//假若还没有数据到循环队列并且当前进程没有受到信号
{
shedule();//进程调度
current->state=TASK_INTERRUPTIBLE;
}
current->state=TASK_RUNNING;
remove_wait_queue(&queue,&wait);
if(head==tail)
return count;
t=get_data();//调用get_data()函数,得到缓冲区中的数据,下面将给予详细的 介绍
out_buf[0]=t.status;
out_buf[1]=t.click;
copy_to_user(buf,&out_buf,sizeof(out_buf));//将得到的键值拷贝到用户数据区
return count;
}
}
很自然我们就应该要介绍get_data()函数的实现了,该函数的功能就是从我们定义的循环队列缓冲区中读出我们要的键值,所以其实很简单的如果理解循环队列的原理,在此不多加解释,大家应该具备一般的数据结构相关的知识吧
static KEY_EVENT get_data(void)
{
int last=tail
if(++tail==BUFSIZE)
tail=0;
return buf[last];
}
上面如果你看得懂得话,那么可以进入下面的学习了,主要介绍的是内核定时器的使用,利用等待队列实现阻塞型I/O,poll系统调用,异步通知方式,介绍完之后,我将给出一个应用实例,对于有使用过文件操作系统调用的来说,对我们所写的键盘驱动来说,他们基本上是一样的。废话少说,我们马上开始我们精彩的驱动开发!

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wzyggx.html