- 编写实际的驱动函数类似于 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 目录下面的信息,自动创建节点。