分类: 嵌入式
2015-07-03 21:47:16
字符设备驱动(新)
15年6月6月18月18日18日日12:10:17
在之前的字符设备驱动程序中,驱动程序注册用register_chrdev()函数来完成,这个是在2.4内核中使用的方法。这种方法有个缺点,只能使用256个设备,比如对于同一个主设备号,次设备号0~255都属于这个主设备号的设备。在2.6内核中,抛弃了这种方法,目的是想把同一个主设备号,但是不同的次设备号分配给不同的设备。
先看代码:
1
2 #include <linux/module.h>
3 #include <linux/kernel.h>
4 #include <linux/fs.h>
5 #include <linux/init.h>
6 #include <linux/delay.h>
7 #include <linux/irq.h>
8 #include <asm/uaccess.h>
9 #include <asm/irq.h>
10 #include <asm/io.h>
11 #include <asm/arch/regs-gpio.h>
12 #include <asm/hardware.h>
13 #include <linux/poll.h>
14 #include <linux/cdev.h>
15
16 static int major;
17 static int hello_open(struct inode *inode, struct file *file)
18 {
19 printk("hello_open\n");
20 return 0;
21 }
22
23 static int hello2_open(struct inode *inode, struct file *file)
24 {
25 printk("hello2_open\n");
26 return 0;
27 }
28
29 static struct file_operations hello_fops = {
30 .owner = THIS_MODULE,
31 .open = hello_open,
32 };
33
34 static struct file_operations hello2_fops = {
35 .owner = THIS_MODULE,
36 .open = hello2_open,
37 };
38
39 #define HELLO_CNT 2
40
41 static struct cdev hello_cdev;
42 static struct cdev hello2_cdev;
43 static struct class *cls;
44
45 static int hello_init(void)
46 {
47 dev_t devid;
48 int i = 0;
49 #if 0
50 major = register_chrdev(0, "hello", hello_fops);
51 #else
52 if (major)
53 {
54 devid = MKDEV(major, 0);
55 register_chrdev_region(devid, HELLO_CNT, "hello");
56 }
57 else
58 {
59 alloc_chrdev_region(&devid, 0, HELLO_CNT, "hello");
60 major = MAJOR(devid);
61 }
62
63 cdev_init(&hello_cdev, &hello_fops);
64 cdev_add(&hello_cdev, devid, HELLO_CNT);
65
66 devid = MKDEV(major, 2);
67 register_chrdev_region(devid, 1, "hello2");
68 cdev_init(&hello2_cdev, &hello2_fops);
69 cdev_add(&hello2_cdev, devid, 1);
70 #endif
71
72 cls = class_create(THIS_MODULE, "hello");
73 for(i = 0; i < 4; i++)
74 {
75 class_device_create(cls, NULL, MKDEV(major, i), NULL, "hello%d", i);
76 }
77
78 return 0;
79 }
80
81 static void hello_exit(void)
82 {
83 int i;
84 class_destroy(cls);
85 for(i = 0; i < 4; i++)
86 {
87 class_device_destroy(cls, MKDEV(major, i));
88 }
89
90 cdev_del(&hello_cdev);
91 unregister_chrdev_region(0, HELLO_CNT);
92
93 cdev_del(&hello2_cdev);
94 unregister_chrdev_region(2, 1);
95
96 }
97
98 module_init(hello_init);
99 module_exit(hello_exit);
100
101 MODULE_LICENSE("GPL");
102
先讲解一下基础知识:
在内核中,用dev_t类型来保存设备编号,在2.6内核中,dev_t是一个32位的数,其中12位用来表示主设备号,其余的20位来表示次设备号。要获得dev_t的主设备号或次设备号,应使用:
MAJOR(dev_t dev);
MINOR(dev_t dev);
相反,如果需要将主设备号和次设备号转换成dev_t类型,则使用:
MKDEV(int major, int minor);
如果知道或者指定了设备的主设备号,在注册的时候使用register_chrdev_region()函数,函数原型如下:
int register_chrdev_region(dev_t from, unsigned count, const char *name);
第一个参数是次设备号的起始值,第二个参数是想申请的个数,从起始值开始的个数,第三个参数是设备的名称。
如果提前不知道设备将要使用哪些主设备号,则需要使用动态分配函数:
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
分配主设备号的最佳方式是:默认采用动态分配,同时保留在加载甚至是编译时指定主设备号的余地。所以推荐使用下面类似的方法来进行分配:
if (major)
{
devid = MKDEV(major, 0);
register_chrdev_region(devid, HELLO_CNT, "hello");
}
else
{
alloc_chrdev_region(&devid, 0, HELLO_CNT, "hello");
major = MAJOR(devid);
}
每个字符设备对应一个cdev结构体,结构体如下:
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
注册完以后,调用 cdev_init()和 cdev_add()函数将字符设备的操作函数file_operations结构体和cdev结合在一起:
void cdev_init(struct cdev *cdev, const struct file_operations *fops);
int cdev_add(struct cdev *p, dev_t dev, unsigned count);
本例中用下面两条语句:
cdev_init(&hello_cdev, &hello_fops);
cdev_add(&hello_cdev, devid, HELLO_CNT);
下面再来理解这个程序的目的:想要动态分配一个主设备号给两个设备,即两个设备对应同一个主设备号,但是次设备号不同,次设备号0~1分配给hello,次设备号2分配给hello2。
通过一个测试程序来测试是否能够这样分配:
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <sys/types.h>
5 #include <sys/stat.h>
6 #include <fcntl.h>
7
8
9 /*
10 * hello_test /dev/hello0
11 */
12
13 void print_usage(char *file)
14 {
15 printf("%s <dev>\n", file);
16 }
17
18 int main(int argc, char **argv)
19 {
20 int fd;
21 if (argc != 2)
22 {
23 print_usage(argv[0]);
24 return 0;
25 }
26
27 fd = open(argv[1], O_RDWR);
28 if (fd < 0)
29 printf("can't open %s\n", argv[1]);
30 else
31 printf("can open %s\n", argv[1]);
32
33 return 0;
34 }
可以看到,hello和hello2的主设备号相同。