李亚锋 2009-06-16 16:53
基于ARM9 开发板的按键 字符 设备驱动实现
摘要:
该驱动程序实现4 个按键设备在 Linux 系统中基于 QT2410E开发板 的工作情况,通过该实例可以了解ARM 平台 Linux 系统下的 GPIO 程序控制,以及硬件中断程序的工作机制。另外还可以熟悉 Linux 2.6 内核的模块加载和测试方法。
1.了解硬件原理图
由于该设备驱动是针对具体硬件设备的,所以一般需要了解它的硬件原理图(如图1 ),该模块有四个按键分别是 S2 , S3 , S4 和 S5 ,其中这四个按键分别对应的外部中断为: EINT0 , EINT2 , KBDINT ( EINT1 )和 KBDSPIMISO ( EINT13 )。工作原理很简单,当系统正常工作时,按这四个任意键会产生相应的中断信号,从而系统会知道哪个按键被触发,在该驱动实现中当有哪个按键被触发时会有相应的打印信息产生。
图1 按键模块的电路图
2.设计驱动程序框架
字符设备指那些必须以串行顺序依次访问的设备 ,并且不需要 缓冲 ,通常用于不需要大量数据请求传送的设备类型,所以对于该按键设备适合用字符设备类型来实现。对于Linux 设备驱动,通常根据设备类型的不同所以实现框架会有所不同,比如字符设备、块设备和网络设备分别都有自己的实现框架和相应的内核 API 函数。通常字符设备驱动实现的内容有:模块的加载(字符设备的注册,硬件的初始化,中断的注册等);字符设备的操作函数实现( open , close , read , write 和 ioctl 等);中断程序的实现;模块的卸载(注销字符设备,注销其他申请的资源)。下面来分析一下该按键设备驱动的具体实现。
3. 按键模块的加载
1 static int __init buttons_init(void)
2 {
3 int ret,devno;
4 dev_t dev;
5 ret = alloc_chrdev_region(&dev,0,1,DEVICE_NAME); //在系统中申请一个字符设备区域,主设备号由系统动态分配。
6 buttons_major_number = MAJOR(dev); //摘取出主设备号
7 printk(KERN_INFO "Initial QT2410E Board Buttons driver!/n");
8 if (ret<0) {
9 printk(KERN_WARNING "button:can't get major number %d/n",buttons_major_number);
10 return ret;
11 }
12 ret = request_irqs(); //注册中断,下文会具体分析该函数的实现
13 if (ret) { //如果注册中断失败,则注销上面申请的字符设备区域。
14 unregister_chrdev_region(dev,1);
15 printk(KERN_WARNING "button:can't request irqs/n");
16 return ret;
17 }
18 devno = MKDEV(buttons_major_number,0);
19 cdev_init(&buttons_dev,&buttons_fops); //初始化 buttons_dev 字符设备结构
20 buttons_dev.owner = THIS_MODULE;
21
22
23 ret = cdev_add(&buttons_dev,devno,1);// 将字符设备加入到内核中
24 if (ret) { //如果添加失败,则做上述注册的释放操作。
25 free_irqs();
26 unregister_chrdev_region(dev,1);
27 printk(KERN_NOTICE "Error %d adding buttons device/n",ret);
28 return ret;
29 }
30
31 #ifdef CONFIG_DEVFS_FS //如果定义 devfs ,系统会自动创建 /dev 目录下的字符设备节点,比如这里会自动创建 /dev/buttons 字符设备节点
32 devfs_mk_cdev(MKDEV(buttons_major_number,0), S_IFCHR | S_IRUSR | S_IWUSR,DEVICE_NAME);
33 printk(KERN_INFO"/dev/%s has been added to your system./n",DEVICE_NAME);
34 #else //否则需要执行 mknod 命令手动创建字符设备节点。
35 printk(DEVICE_NAME "Initialized/n");
36 printk(KERN_INFO "You must create the dev file manually./n");
37 printk(KERN_INFO "Todo: mknod c /dev/%s %d 0/n",DEVICE_NAME,buttons_major_number);
38 #endif
39 return 0;
40 }
分析上述代码, buttons_init 是该按键模块的驱动入口函数,也是该内核模块的加载函数,主要用于注册资源、申请资源和初始化设备等工作。第5 行, alloc_chrdev_region ()是内核提供的申请字符设备号函数,它会动态的为设备申请一个主设备号,并且根据输入参数申请多个次设备。第19 行, cdev_init ()是用于初始化一个 cdev 结构,这里初始化的是buttons_dev 全局变量。第 32 行, devfs_mk_cdev ()函数会在/dev 目录下自动创建一个设备节点,早期的内核版本是需要手动通过 mknod 工具创建设备节点。
4.字符设备的操作函数
由于该设备功能单一,所以这里只实现了read 操作,关于 read 操作的定义如下:
static struct file_operations buttons_fops = {
.owner = THIS_MODULE,
.read = buttons_read,
};
其中read 操作是被定义在 file_operations 的对象中,由 buttons_read 函数具体实现。
1 static ssize_t buttons_read(struct file *filp,char __user *buffer,size_t count,loff_t *ppos)
2 {
3 static int key;
4 unsigned long flags;
5 if (!ready) {
6 return -EAGAIN;
7 }
8 if (count != sizeof key_value)
9 return -EINVAL;
10 local_irq_save(flags);
11 key = key_value;
12
13 local_irq_restore(flags);
14 copy_to_user(buffer, &key, sizeof key);
15 ready = 0;
16
17 return sizeof key_value;
18 }
以上代码中最重要的实现是由 copy_to_user ()函数实现的,它是内核提供的用于将内核空间数据拷贝到用户空间中去API ,它是内核空间与用户空间通信的重要实现函数。
5. 中断实现函数
1 static int request_irqs(void)
2 {