led驱动程序、测试程序和makefile文件的代码分析 下面是根据韦东山老师讲课记的笔记,将重要的内容记录下来,下面的 led驱动程序硬件信息是我根据tiny6410的原理图、威廉希尔官方网站
手册所写。终于将驱动程序的框架有了大致的了解,感觉花了这么长的时间值得,以后的框架就能了解了。明天独立写再次编写led驱动程序。Led驱动程序代码(first_drv_led.c)分析: /*我们开始学驱动的头文件参考别人所写的代码得来的*/ #include #include #include #include #include //定义全局变量gpkcon0,gpkdat,这两个寄存器要映射虚拟地址 volatile unsigned long *gpkcon0 =NULL; volatile unsigned long *gpkdat =NULL; //全局变量主设备号 int major; //驱动可以自动分配主设备号也可以手工创建主设备 //应用程序打开设备文件open("dev/xxx");设备文件dev/xxx是怎么来的? //手工建立:mknod /dev/xxx c major minor //自动创建:使用udev机制或者mdev机制 //mdev根据系统信息创建设备节点 //怎么提供系统信息? //定义以下类来提供系统信息 static struct class *first_drv_class; static struct class_device *first_drv_class_devs[4]; static int first_drv_led_open(struct inode *inode, struct file *file) { /*根据原理图和威廉希尔官方网站
手册设置引脚gpk4,5,6,7为输出*/ //按下面方法设置可以防止对gpkcon0其他不需要的位进行操作从而破坏掉别的功能 //清零 *gpkcon0 &= ~((0x03<<(4*2))|(0x03<<(5*2))|(0x03<<(6*2))|(0x03<<(7*2))); //设置输出模式 *gpkcon0 |= ((0x01<<(4*2))|(0x01<<(5*2))|(0x01<<(6*2))|(0x01<<(7*2))); return 0; } static ssize_t first_drv_led_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos) { int val; //测试程序传递进来的值放在buf里面怎么讲测试程序传进来的值取出来? //用copy_from_user,用户空间到内核空间传递数据 //内核空间到用户空间传递数据用copy_to_user(); copy_from_user(&val,buf,count); //判断,操作led if(val==1) { //点灯 *gpkdat &= ~((1<<4)|(1<<5)|(1<<6)|(1<<7)); } else { //灭灯 *gpkdat |= ((1<<4)|(1<<5)|(1<<6)|(1<<7)); } return 0; } /* * 这个结构是字符设备驱动程序的核心 * 当应用程序操作设备文件时所调用的open、read、write等函数, * 最终会调用这个结构中指定的对应函数 *作用就是告诉内核first_drv_led_open等是与应用程序open等函数对应的 *虽然告诉你内核了但是并没有将这个结构体用起来,还得将这个结构体告诉内核 *怎么告诉?用register_chrdev()注册 */ static struct file_operations first_drv_led_fops = { .owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */ .open = first_drv_led_open, .write = first_drv_led_write, }; /* *驱动入口函数 *第一个驱动入口函数first_drv_init,第二个呢second_drv_init *函数名称不同,内核怎么知道你的入口函数是哪一个呢? *所以我们得修饰一下 */ int first_drv_init(void) { //注册,即告诉内核将结构体用起来 //但是谁来调用register_chrdev函数呢?驱动入口函数调用register_chrdev函数 //major是主设备号,应用程序是通过主设备号来找到我们所注册的结构体 register_chrdev(major,"first_drv_led",&first_drv_led_fops); //在驱动程序里面要操作虚拟地址 //映射虚拟地址 //ioremap(开始地址,长度) *gpkcon0=(volatile unsigned long *)ioremap(0x7F008800,16); //因为gpkdat的物理地址是0x7F008808,而且之所以不是+8是因为指针的操作是以他的指向的长度为准 *gpkdat=*gpkcon0+2; //创建类,在这个类下面创建设备 //入口函数创建了在出口函数要删除 //下面两句会自动创建类first_drv在这个类下面自动创建设备first_drv_led //而mdev会自动创建设备节点/etc/first_drv_led //在此基础编译执行会出现一些未识别错误通过添加MODULE_LICENSE("GPL");解决出现的错误问题 first_drv_class = class_create(THIS_MODULE, "first_drv"); first_drv_class_devs = class_device_create(first_drv_class, NULL, MKDEV(major, 0), NULL, "first_drv_led"); return 0; } /*出口函数*/ void first_drv_exit(void) { //卸载驱动 //修饰出口函数告诉内核调用 unregister_chrdev(major."first_drv_led"); //在入口函数映射了虚拟地址在出口函数要去掉映射 iounmap(gpkcon0); //删除入口创建的类 class_device_unregister(first_drv_class_devs); class_destroy(first_drv_class); } //修饰后first_drv_init才成为入口函数 module_init(first_drv_init); //修饰后first_drv_exit才成为出口函数 module_exit(first_drv_exit); //在编译时候会出现一些未识别错误 //MODULE_LICENSE("GPL");解决未识别错误问题 MODULE_LICENSE("GPL"); 测试(fisrt_drv_ledtest.c)代码: #include #include #include #include /* *串口输入信息: *first_drv_led on 代表开灯 *first_drv_led off 代表关灯 */ int main(int argc, char **argv) { int fd; int val; fd=open("/dev/first_drv_led",O_RDWR); //没有打开设备first_drv_led if (fd<0) { printf("error, can't open !n"); return 0; } if (argc !=2) //用法不对,串口输入的字符不是2个。 { printf("usage:n"); printf("%s n",argv[0]); return 0; } //判断输入的第二个参数是否为on if( strcmp ( argv[1], "on" ) == 0) { //如果是on,点灯 val=1; } if( strcmp ( argv[1], "off" ) == 0) { //如果是off,关灯 val=0; } //将打开驱动的信息,VAL值等传入驱动程序中的first_drv_led_write write(fd,&val,4); return 0; } Makefile代码: KERN_DIR = /work/system/linux-2.6.22.6//基于2.6.22.6内核 all: make -C $(KERN_DIR) M=`pwd` modules clean: make -C $(KERN_DIR) M=`pwd` modules clean rm -rf modules.order obj-m += first_drv_led.o 综合上面的驱动程序我们可以总结驱动程序的框架: static int xxxx_open(struct inode *inode, struct file *file) static ssize_t xxxx_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos) { 用copy_from_user,用户空间到内核空间传递数据 copy_from_user(&val,buf,count); } 字符设备驱动的核心:file_operations结构体 static struct file_operations first_drv_led_fops ={}; 入口函数 int first_drv_init(void) { 注册,即告诉内核将结构体用起来 register_chrdev(major,"first_drv_led",&first_drv_led_fops); 映射虚拟地址 ioremap(开始地址,长度) 创建类,在这个类下面创建设备 XXXX_class = class_create(THIS_MODULE, "XXXX"); XXXX_class_devs = class_device_create(XXXX_class, NULL, MKDEV(major, 0), NULL, "XXXX"); } 修饰入口函数 module_init(first_drv_init); 出口函数 void first_drv_exit(void) { 卸载驱动 unregister_chrdev(major."XXXX"); 在入口函数映射了虚拟地址在出口函数要去掉映射 iounmap(gpkcon0); 删除入口创建的类 class_device_unregister(XXXX_class_devs); class_destroy(XXXX_class); } 修饰出口函数 module_exit(first_drv_init); 在编译时候会出现一些未识别错误 MODULE_LICENSE("GPL"); 过程: 将fisrt_drv_ledtest.c、first_drv_led.c和Makefile拷贝到服务器 执行make,生成first_drv_led.ko文件 编译测试程序: ARM-linux-gcc -o fisrt_drv_ledtest fisrt_drv_ledtest.c加载驱动程序insmod first_drv_led.ko 执行测试程序./first_drv_led on或者first_drv_led off
|