linux设备驱动程序开发初探(3) 练习:从零写一个查询式按键驱动程序
步骤:
①先写出驱动程序框架
实例化一个file_operation结构体,其中描述操作
②填充框架,按照原理图操作硬件
-----------------------------------------------------
补充一个内容,经典书籍《深入理解linux内核》中对于设备驱动程序的一个描述,能够帮助理解驱动程序的地位与作用:
“内核通过设备驱动程序(device driver)与IO设备交互。
设备驱动程序包含在内核中,由控制一个或者多个设备的数据结构和函数组成,这些设备包括硬盘、键盘、鼠标、监视器、网络接口及连接到SCSI(andrew注:小型计算机系统接口,主要连接硬盘、软驱、光驱、打印机、扫描仪等设备)总线上的设备。
通过特定的接口,每个驱动程序与内核中的其余部分(甚至与其他驱动程序)相互作用的这种方式有以下优点:
· 可以把特定设备的代码封装在特定的模块中。
· 厂商可以在不了解内核源代码而只知道接口规范的情况下,就能增加新的设备。
· 内核以统一的方式对待所有设备,并且通过相同的接口访问这些设备。
· 可以把设备驱动程序写成模块,并动态地把它们装进内核而不需要重新启动系统。不再需要时,也可以动态地卸下模块,以减少存储在RAM中内核映像的大小。”
-----------------------------------------------------
second.c
----------
拷贝头文件:
#include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/init.h> #include <linux/delay.h> #include <asm/uaccess.h> #include <asm/irq.h> #include <asm/io.h> #include <asm/arch/regs-gpio.h> #include <asm/hardware.h>
实例化一个file_operation结构体,其中描述操作,写出框架:
static struct file_operations second_drv_fops = { .owner = THIS_MODULE, .open = second_drv_open, .write = second_drv_read, };
实现操作框架:
static int second_drv_open(stuct inode *inode, struct file *file) { return 0; } ssize_t second_drv_read(struct file *file,char __user *buf,size_t size,loff_t *ppos) //注意返回的数据类型 { return 0; } //入口注册函数: int major; static int second_drv_init(void) { major = register_chrdev(0,"second_drv",&second_drv_fops); return 0; } //卸载函数: static void second_drv_exit(void) { unregister_chrdev(major,"second_drv"); } //修饰为内核统一的接口: module_init(second_drv_init); module_exit(second_drv_exit);
给sys提供更多的信息,供mdev机制使用。程序开头定义两个类:
static struct class *seconddrv_class; static stuct class_device *seconddrv_class_dev;
在入口函数中创建类,在类下面创建设备:
seconddrv_class = class_create(THIS_MODULE, "seconddrv"); seconddrv_class_dev = class_device_create(seconddrv_class, NULL, MKDEV(major, 0), NULL, "buttons");//设备节点的名字叫buttons
在卸载函数中卸载:
class_device_unregister(seconddrv_class_dev); class_destroy(seconddrv_class); //最后加上license MODULE_LICENSE("GPL");
至此,框架编写完成,下面加入硬件功能
======================================================================
假设我们要查询s3c2440这个SoC的GPF0、GPF2、GPG3、GPG11四个引脚的按键情况。
首先要在驱动程序的
开头定义寄存器:
volatile unsigned long *gpfcon; volatile unsigned long *gpfdat; volatile unsigned long *gpgcon; volatile unsigned long *gpgdat;
入口函数里进行地址映射 (物理地址===>虚拟地址):
gpfcon = (volatile unsigned long *)ioremap(0x56000050,16); //io remap 重映射 gpfdat = gpfcon + 1; gpgcon = (volatile unsigned long *)ioremap(0x56000060,16); gpgdat = gpfcon + 1;
//在出口函数中解除地址映射关系: iounmap(gpfcon); iounmap(gpgcon); //在open函数里配置引脚(配置GPF0、GPF2、GPG3、GPG11为输入): *gpfcon &= ~( (0x3<<(0*2)) | (0x3 << (2*2))); *gpgcon &= ~( (0x3<<(3*2)) | (0x3 << (11*2)));
在read函数里查询按键值(返回4个引脚的电平):
定义一个数组存储键值
unsigned char key_vals[4]; int regval; if (size != sizeof(key_vals)) //验证是否传入4数据大小的buf return -EINVAL; //返回错误值 regval = *gpfdat; key_vals[0] = (regval & (1<<0)) ? 1:0; key_vals[1] = (regval & (1<<2)) ? 1:0; regval = *gpgdat; key_vals[2] = (regval & (1<<3)) ? 1:0; key_vals[3] = (regval & (1<<11)) ? 1:0; copy_to_user(buf,key_vals,sizeof(key_vals)); //从内核向用户空间buf拷贝数据,长度为size(key_vals),sizeof返回数组的大小 return sizeof(key_vals);
==============================================================
最后,编写Makefile进行编译链接,并加载模块,用下面的程序测试:
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> /* seconddrvtest */ int main(int argc, char **argv) { int fd; unsigned char key_vals[4]; int cnt = 0; fd = open("/dev/buttons", O_RDWR); if (fd < 0) { printf("can't open!\n"); } while (1) { read(fd, key_vals, sizeof(key_vals)); if (!key_vals[0] || !key_vals[1] || !key_vals[2] || !key_vals[3]) { printf("%04d key pressed: %d %d %d %d\n", cnt++, key_vals[0], key_vals[1], key_vals[2], key_vals[3]); } } return 0; }
=====================================================================