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

发布时间 2023-08-18 17:08:19作者: fuzidage

1 引入misc device

1.1传统cdev方式

char_drv.c
#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");
总结流程:
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_create和device_create操作。misc_register时会自行调用了 class_create(), device_create() 因此 /sys/class/misc 类会被创建, /dev/下的设备节点也会自动创建。
/proc/misc记录了系统中所有加载的misc设备:
image

点击查看代码
#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设备注册过程

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
当用户调用open("/dev/xxx")时,由于misc设备主设备号都为10,那么会统一进入到misc_open,那么会根据次设备号来区分不同的misc设备,首先iminor(inode)取出次设备号,i_rdev是对应具体misc设备的设备号dev_t。
image
然后遍历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(后面会介绍作用)

/*
* 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:

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

image

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

引入:

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;
	...
}

方法1:(对于misc设备)

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

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

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

方法2:(对于cdev设备)

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);
}

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

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

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保存设备信息。