字符设备驱动-misc杂项设备

1 引入misc device#

1.1 传统cdev方式#

char_drv.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.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 <linux/cdev.h>
#include <linux/device.h>
static int led_major;
struct cdev cdev;
static int led_drv_open(struct inode *inode, struct file *file)
{
return 0;
}
static ssize_t led_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
return 0;
}
static struct file_operations led_drv_fops = {
.owner = THIS_MODULE,
.open = led_drv_open,
.write = led_drv_write,
};
static void led_setup_cdev(void)
{
int err, devno = MKDEV(led_major, 0);//index 为从设备号
cdev_init(&cdev, &led_drv_fops);
cdev.owner = THIS_MODULE;
cdev.ops = &led_drv_fops;
err = cdev_add(&cdev, devno, 1);//devno 为第一个设备号,1为数量
if (err)
printk(KERN_NOTICE "Error %d adding", err);
}
static int led_drv_init(void)
{
int result;
dev_t devno;
struct class *led_class;
struct device *dev;
devno=MKDEV(led_major,0);
if(led_major)//静态申请设备号
result=register_chrdev_region(devno,1,"led1_dev");
else{
result = alloc_chrdev_region(&devno,0,1,"led1_dev");//动态申请设备号
led_major = MAJOR(devno);
}
if(result<0) {
printk (KERN_WARNING "hello: can't get major number %d\n", led_major);
return result;
}

led_setup_cdev();
led_class = class_create(THIS_MODULE, "led_class");
dev = device_create(led_class, NULL, devno, NULL, "%s", "led_dev");
if (IS_ERR(dev)) {
dev_err(dev, "device create failed error code(%ld)\n", PTR_ERR(dev));
return PTR_ERR(dev);
}
return 0;
}
static void led_drv_exit(void)
{
device_destroy(led_class, dev); /* remove the device */
class_destroy(led_class); /* remove the device class */
cdev_del(&cdev);
unregister_chrdev_region(MKDEV(led_major,0),1);
}
module_init(led_drv_init);
module_exit(led_drv_exit);
MODULE_LICENSE("GPL");
总结流程:
1
2
3
4
5
6
A:创建设备号。MKDEV(major_no,0),其值为一个整数。因为linux中使用设备号来关联相应的设备和设备对于的驱动程序。
B:注册设备号。register_chrdev_region(devno,1,"led1_dev")
或者alloc_chrdev_region(&devno,0,1,"led1_dev");//动态申请设备号
C:初始化并关联file_operations结构体。 cdev_init(&cdev, &led_drv_fops);
D:添加字符设备到内核。int cdev_add(struct cdev *p, dev_t dev, unsigned count),
E:移除字符设备及设备号。cdev_del(&cdev); unregister_chrdev_region(MKDEV(led_major,0),1);

kdev_t.h
image

上面涉及到的API可以在函数linux/fs/char_dev.c中找到定义。

1.2 misc device方式#

使用misc_register,在加载模块时会自动创建设备节点,为主设备号为10的字符设备。使用misc_deregister,在卸载模块时会自动删除设备节点。因此无需调用cdev这一套框架流程,无需调用class_createdevice_create操作。misc_register时会自行调用了 class_create(), device_create() 因此/sys/class/misc类会被创建, /dev/下的设备节点也会自动创建。
/proc/misc记录了系统中所有加载的misc设备:
image

点击查看代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include <linux/miscdevice.h>
#include <linux/delay.h>
#include <asm/irq.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/mm.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/delay.h>
#include <linux/moduleparam.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/ioctl.h>
#include <linux/cdev.h>
#include <linux/string.h>
#include <linux/list.h>
#include <linux/pci.h>
#include <asm/uaccess.h>
#include <asm/atomic.h>
#include <asm/unistd.h>
struct led_dev {
struct miscdevice miscdev;
void *data;
}
struct led_dev my_led;
static int leds_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg){
return 0;
}
static int leds_open(struct inode *inode, struct file *filp){
//filp->private_data = &my_led;
return 0;
}
static int leds_release(struct inode *inode, struct file *filp){
return 0;
}
static ssize_t leds_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos){
return 1;
}

static struct file_operations leds_fops ={
.owner = THIS_MODULE,
.read = leds_read,
.ioctl = leds_ioctl,
.open = leds_open,
.release = leds_release
};
static int __init dev_init(void){
struct miscdevice *miscdev = &my_led.miscdev;
miscdev->minor = MISC_DYNAMIC_MINOR,
miscdev->name = "misc_leds",
miscdev->fops = &leds_fops,
miscdev->parent = NULL;
int ret = misc_register(miscdev);
return ret;
}
static void __exit dev_exit(void){
misc_deregister(&my_led.miscdev);
}
module_init(dev_init);
module_exit(dev_exit);
MODULE_LICENSE("GPL");

2 misc杂项设备解析#

源代码位置driver/char/misc.c,主设备号固定为10,所有的miscdevice设备形成了一个链表,对设备访问时内核根据次设备号查找对应的miscdevice设备,然后调用其file_operations结构中注册的文件操作接口进行操作。

2.1 misc_init过程#

image
misc子系统的初始化是利用subsys_initcall进行子系统初始化,首先创建/proc/misc条目,对应cat /proc/misc可以看到所有misc设备信息,cat /proc/misc于是就会调用misc_seq_ops中的misc_seq_show函数,可以看到刚好为misc设备的次设备号和名字信息。
image
主设备号固定为10,调用class_create创建/sys/class/misc, 调用register_chrdev注册字符设备,添加file_operations。(register_chrdev如果传入主设备号,则静态注册,否则动态注册返回主设备号)
image
image
image

2.2 misc设备注册misc_register过程#

image

MISC_DYNAMIC_MINOR = 255,使用者调用misc_register时一般会次设备号传入MISC_DYNAMIC_MINOR,那么会自动分配次设备号;否则遍历misc_list链表,看这个次设备号以前有没有被用过,如果次设备号已被占有则退出返回-EBUSY。
得到这个次设备号后set_bit(i, misc_minors);设置位图中相应位为1。device_create_with_groups等同于device_create创建设备节点。
最后将list节点添加到misc_list链表中。
cat /sys/class可以看到所有驱动中调用class_creat()函数的模块,cat /sys/class/misc则可以看到所有misc杂项驱动模块。ls /dev/*可以看到对应的设备节点
image
image
image

2.3 misc设备卸载过程#

image
misc_list链表中删除节点list, 然后删除设备节点。释放位图相应位清0,以便次设备号留给下一个模块使用。

2.4 misc设备打开过程#

image

  1. 当用户调用open("/dev/xxx")时,由于misc设备主设备号都为10,那么会统一进入到misc_open,那么会根据次设备号来区分不同的misc设备,首先iminor(inode)取出次设备号,i_rdev是对应具体misc设备的设备号dev_t
    image
  2. 然后遍历misc_list链表,找到与minor次设备号相匹配的misc device,找到后将file_operations(简称fops)暂存到new_fops。如果匹配不到,则请求加载这个次设备号对应的模块。request_module表示让linux系统的用户空间调用/sbin/modprobe函数加载名为char-major-%d-%d的模块。
    image
    匹配成功后file->private_data = c;表示将链表中匹配出的miscdevice作为file->private_data(后面会介绍作用)
1
2
3
4
5
6
/*
* Place the miscdevice in the file's
* private_data so it can be used by the
* file operations, including f_op->open below
*/
file->private_data = c;

最后将暂存的new_fops赋值给file->f_op,调用具体的misc模块的fops:

1
file->f_op->open(inode, file);

image

3 如何从fops中获取模块设备信息#

3.1 引入#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct xxx_dev {
struct miscdevice miscdev;
void *data;
};

static long dwa_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct xxx_dev *m = container_of(filp->private_data, struct xxx_dev, miscdev);
...
return 0;
}
static int __init xxx_init(void) {
struct xxx_dev *m;
...
}

3.1.1 方法1:(对于misc设备)#

可以看到如果我们想要重file_oprations获取设备入口,可以通过如下方式:

1
struct xxx_dev *m = container_of(filp->private_data, struct xxx_dev, miscdev);

前面2.4讲过了匹配成功后file->private_data = c;表示将链表中匹配出的miscdevice作为file->private_data.

3.1.2 方法2:(对于cdev设备)#

1
2
3
4
5
6
7
8
9
10
struct xxx_dev {
struct cdev cdev;
void *data;
};
int xxx_open(struct inode *inode, struct file *file)
{
int ret = 0;
struct xxx_dev *m;
m = container_of(inode->i_cdev, struct xxx_dev, cdev);
}

inodei_cdev指向的即为cdev结构体。调用container_of即可获取设备信息。

3.1.3方法3:xxx_open中保存设备信息#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct xxx_dev {
struct cdev cdev;
void *data;
struct resource* res;
};
struct xxx_dev *m;
static long keyscan_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
struct xxx_dev *m = file->private_data;
uint32_t res_size = (uint32_t)resource_size(m->res);
}
int xxx_open(struct inode *inode, struct file *file)
{
file->private_data = m;
}

file->private_data = m保存设备信息。