字符设备驱动
linux 字符设备驱动学习。
- 编写实际的驱动函数类似于 led_open, led_write 等。
- 定义一个 file_operations 结构体,把 led_open 等实际的驱动函数填充进去。
- 调用 register_chrdev 函数,把 file_operations 结构体注册到内核里面去。
- 调用 register_chrdev 的这个函数,一般是 led_init ,也叫做驱动的入口函数。
- 使用 module_init(led_init); 这个宏来修饰 led_init 这个入口函数。内核会寻找 module_init 这个结构体,找到之后就会调用里面的函数指针 led_init 来调用驱动的入口函数了。
- 驱动注册之后,/dev/ 下面的设备一般是字符设备,就是属性是 c 开头的,还有相应的主设备号 major,和次设备号 minor。
- 在内核的 vfs 里面,有一个数组,这个数组每一位指向一个主设备号,register_chrdev 就是把 file_operations 挂接到这个数组的主设备号上面。内核需要调用相应的驱动函数,也是按照主设备号去这个数组里面去找相应的 file_operations 来进行操作。
- 出口函数 led_exit 里面调用 卸载函数 unregister_chrdev,然后用 module_exit 这个宏来修饰。
- 复制其他驱动下面的 makefile 到当前驱动的目录,简单的只需要修改 obj-m += 这一项为当前的驱动 即可。然后 make 即可生成驱动。
- 使用 cat /proc/devices 来查看内核目前支持的设备。
-
在 tq2440 里面编写的代码如下:
1 #include <linux/module.h> 2 #include <linux/kernel.h> 3 #include <linux/fs.h> 4 #include <linux/init.h> 5 #include <linux/delay.h> 6 #include <asm/uaccess.h> 7 #include <asm/irq.h> 8 //#include <asm.io.h> 9 //#include <asm/arch/regs-gpio.h> 10 //#include <asm/hardware.h> 11 12 13 static int first_drv_open(struct inode *inode, struct file *file) 14 { 15 printk("first_drv_open\n"); 16 return 0; 17 } 18 19 static ssize_t first_drv_write(struct file *file, const char __user *buf, si ze_t count, loff_t * ppos) 20 { 21 printk("first_drv_write\n"); 22 return 0; 23 } 24 25 static struct file_operations first_drv_fops = { 26 .owner = THIS_MODULE, 27 .open = first_drv_open, 28 .write = first_drv_write, 29 }; 30 31 int first_drv_init(void) 32 { 33 register_chrdev(111, "first_drv", &first_drv_fops); 34 return 0; 35 } 36 37 void first_drv_exit(void) 38 { 39 unregister_chrdev(111, "first_drv"); 40 return; 41 } 42 43 module_init(first_drv_init); 44 module_exit(first_drv_exit);
makefile
1 KERN_DIR = /home/book/embed_sky/linux-2.6.30.4 2 CC = arm-linux-gcc 3 4 all: 5 make -C $(KERN_DIR) M=`pwd` modules 6 7 clean: 8 make -C $(KERN_DIR) M=`pwd` modules clean 9 rm -rf modules.order 10 11 obj-m += first_drv.o
-
make 编译出 ko 文件,然后复制到 nfs_root 目录。 切换到开发板上,使用 insmod xxx.ko 命令,加载驱动, 然后 cat /proc/devices,可以看到已经加载上驱动设备了。
-
编写驱动测试程序:
1 #include <sys/types.h> 2 #include <sys/stat.h> 3 #include <fcntl.h> 4 #include <stdio.h> 5 6 int main(int argc, char **argv) 7 { 8 int fd; 9 int val = 1; 10 fd = open("/dev/xxx", O_RDWR); 11 if (fd <0) 12 printf("can't open!\n"); 13 write(fd, &val, 4); 14 return 0; 15 }
然后 arm-linux-gcc -o first_test first_test.c 再把生成的文件复制到 nfs 里面去。
-
在开发板上执行 first_test, 提示 can't open! 因为没有 /dev/xxx 这个设备。 所以我们先创建这个设备: mknod /dev/xxx c 111 0 然后再次执行,就可以了。
-
在驱动程序里面可以不指定主设备号,让系统自动分配,这样程序就改为:
31 int major; 32 33 int first_drv_init(void) 34 { 35 major = register_chrdev(0, "first_drv", &first_drv_fops); 36 return 0; 37 } 38 39 void first_drv_exit(void) 40 { 41 unregister_chrdev(major, "first_drv"); 42 return; 43 }
然后重新编译并复制到 nfs 里面。 在开发板上,使用 rmmod first_drv 来卸载模块,然后 lsmod 确定模块卸载了, 然后使用 inmod 重新加载模块, cat /proc/devices 来看看分配到什么设备号了。 然后 rm /dev/xxx 删除原来的设备, mknod /dev/xxx c 252 0 按照新的设备号重新建立设备,这样就测试程序又可以正常工作了。
- 如果想要自动创建节点,那么就需要 udev,或者简化后的 mdev 来实现,mdev 根据 /sys/devices 目录下面的信息,自动创建节点。