Linux device driver model character device

Linux device driver model character device

We have introduced the Linux device tree before. In this section we will introduce the character device driver. A character device is a device that transmits characters in units of characters during IO transmission, and a character device driver is a piece of code that can drive a character device driver. What does a character device driver look like in Linux currently? Let’s discuss and learn about it. .

Basic knowledge

character device framework

If you were asked to design a character device framework, how would you design it? Different developers will have different needs, but everyone needs to register character devices and need a place to save and manage these device driver information. How to save it in the code will be more flexible and appropriate?

Register character device

The current Linux kernel defines a driver through the major device number and the minor device number. The major device number ranges from 0 to CHRDEV_MAJOR_MAX(512) - 1, a total of 512 major device numbers, while the minor device number ranges from 0 to 2 20 - 1 (MINORMASK definition). Therefore, the first step for a character device driver is to register a device number with the kernel:

#define DAO_NAME        "dao"
static dev_t            dao_devt;

// 注册字符设备函数调用
alloc_chrdev_region(&dao_devt, 0, MINORMASK + 1, DAO_NAME);	//注册字符设备号

/** 注册字符设备函数声明,通过下面我们可以知道,
 * dev 是保存主设备号,
 * baseminor 则是代表可以从该索引开始查找可使用的次设备号
 * count 代表次设备号可搜索的范围
 * name 则是该字符设备的名称
 */
/**
 * alloc_chrdev_region() - register a range of char device numbers
 * @dev: output parameter for first assigned number
 * @baseminor: first of the requested range of minor numbers
 * @count: the number of minor numbers required
 * @name: the name of the associated device or driver
 *
 * Allocates a range of char device numbers.  The major number will be
 * chosen dynamically, and returned (along with the first minor number)
 * in @dev.  Returns zero or a negative error code.
 */
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
                        const char *name);

And how does alloc_chrdev_region() register the character device number? Let's take a look at its implementation.


/*
 * Register a single major with a specified minor range.
 *
 * If major == 0 this function will dynamically allocate an unused major.
 * If major > 0 this function will attempt to reserve the range of minors
 * with given major.
 *
 */
static struct char_device_struct *
__register_chrdev_region(unsigned int major, unsigned int baseminor,
			   int minorct, const char *name)
{
    
    
	struct char_device_struct *cd, *curr, *prev = NULL;
	int ret;
	int i;

    /* 先检查需要申请的主设备号是否在合理范围之内:0 ~ CHRDEV_MAJOR_MAX(512) - 1 */
	if (major >= CHRDEV_MAJOR_MAX) {
    
    
		pr_err("CHRDEV \"%s\" major requested (%u) is greater than the maximum (%u)\n",
		       name, major, CHRDEV_MAJOR_MAX-1);
		return ERR_PTR(-EINVAL);
	}

    /* 再检查次设备号是否在合理范围之内:0 ~ MINORMASK */
	if (minorct > MINORMASK + 1 - baseminor) {
    
    
		pr_err("CHRDEV \"%s\" minor range requested (%u-%u) is out of range of maximum range (%u-%u) for a single major\n",
			name, baseminor, baseminor + minorct - 1, 0, MINORMASK);
		return ERR_PTR(-EINVAL);
	}

    /* 申请一块内存,该内存将是保存字符设备信息的,内核通过结构体(struct char_device_struct)来存储该信息*/
	cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);
	if (cd == NULL)
		return ERR_PTR(-ENOMEM);

	mutex_lock(&chrdevs_lock);

    /* 如果调用该函数时传递进来的主设备号为0,则代表内核动态分配主设备号,
     * 此时通过 find_dynamic_major() 搜索可用的主设备号
     */
	if (major == 0) {
    
    
		ret = find_dynamic_major();	/* 动态搜索可用的主设备号 */
		if (ret < 0) {
    
    
			pr_err("CHRDEV \"%s\" dynamic allocation region is full\n",
			       name);
			goto out;
		}
		major = ret;	/* 保存搜索到的主设备号到major */
	}

	ret = -EBUSY;
	i = major_to_index(major);
    /* 确认主、次设备号是否可用 */
	for (curr = chrdevs[i]; curr; prev = curr, curr = curr->next) {
    
    
		if (curr->major < major)
			continue;

		if (curr->major > major)
			break;

		if (curr->baseminor + curr->minorct <= baseminor)
			continue;

		if (curr->baseminor >= baseminor + minorct)
			break;

		goto out;
	}

    /* 保存字符设备的主、次设备号、设备名称 */
	cd->major = major;
	cd->baseminor = baseminor;
	cd->
        
        = minorct;
	strlcpy(cd->name, name, sizeof(cd->name));

	/* 将新注册的字符设备添加到chrdevs */
	if (!prev) {
    
    
		cd->next = curr;
		chrdevs[i] = cd;
	} else {
    
    
		cd->next = prev->next;
		prev->next = cd;
	}

	mutex_unlock(&chrdevs_lock);
    /* 返回配置了设备号的char_device_struct */
	return cd;
out:
	mutex_unlock(&chrdevs_lock);
	kfree(cd);
	return ERR_PTR(ret);
}

In alloc_chrdev_region, there are searches for available major device numbers and saving device information to chrdevs. What exactly are they?

We asked above, if you were to design this character device driver framework, how would you design it? How would the kernel be designed?

// fs/char_dev.c

#define CHRDEV_MAJOR_HASH_SIZE 255

static struct char_device_struct {
    
    
        struct char_device_struct *next;
        unsigned int major;
        unsigned int baseminor;
        int minorct;
        char name[64];
        struct cdev *cdev;              /* will die */
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];

The kernel defines a chrdevspointer array called to store character device information. This pointer points to struct char_device_structa structure. chrdevs has a total of 255 members, which are sorted by major device number. In find_dynamic_major(), the search for available major device numbers starts from the end of the chrdevs array to CHRDEV_MAJOR_DYN_END (234). If any element is empty, the index is returned directly to find the valid major device number. Simply put, it is to search from 234 to 254, from back to front. If the array element is empty, the array index is returned as the major device number. If the dynamic allocation (234 254) cannot be found, start the search from 314 to 511. Note that chrdevs only has 255 elements, so the kernel converts the major_to_indexmajor device number into the index of cherdev, which is actually an element of chrdevs. Shared by two character devices, for example, the device with major device number 59 and the device with major device number 314 share a chrdevs element chrdevs[59]. At this time, check whether the chrdevs element is empty. If it is empty, return directly. If the main device in the element has been filled, search for the chrdevs element of the next index.

static int find_dynamic_major(void)
{
    
    
        int i;
        struct char_device_struct *cd;

    	/* 234~254是否为空闲的,为空闲则返回 */
        for (i = ARRAY_SIZE(chrdevs)-1; i >= CHRDEV_MAJOR_DYN_END; i--) {
    
    
                if (chrdevs[i] == NULL)
                        return i;
        }

    	/*234~254已经被占用了,从314~511开始查找 */
        for (i = CHRDEV_MAJOR_DYN_EXT_START;
             i >= CHRDEV_MAJOR_DYN_EXT_END; i--) {
    
    
                for (cd = chrdevs[major_to_index(i)]; cd; cd = cd->next)
                    	/* 确认chrdevs元素对应的大于255的主设备号已经被占用了,
                    	 * 则从再下一个主设备开始查找,该设备号已经被占用
                    	 */
                        if (cd->major == i)	
                                break;

                if (cd == NULL)
                        return i;
        }

        return -EBUSY;
}

In other words, each chrdevs element can represent two major device numbers, one is the index i of the element, and the other is i+255. If both are used, the search can only continue from the next element.

Summary :

The kernel character device saves character device information through the global static pointer array chrdevs. The array has a total of 255 elements. Each element can save character device information of two different major device numbers. Therefore, one kernel can apply for 512 different major device number characters. equipment.

After the driver calls alloc_chrdev_region()to register the device number, the terminal can see the following information:

root@root:/# cat /proc/devices
Character devices:
  1 mem
  4 /dev/vc/0
  4 tty
  5 /dev/tty
  5 /dev/console
  5 /dev/ptmx
  7 vcs
 10 misc
 13 input
 89 i2c
 90 mtd
116 alsa
128 ptm
136 pts
153 spi
180 usb
189 usb_device
247 ubi0
248 ttyS
249 hidraw
250 rpmb
251 dao		// 这里就是我们注册的字符设备,可以看到它的主设备号是251,名字叫dao
252 watchdog
253 rtc
254 gpiochip

Block devices:
  8 sd
 31 mtdblock
 65 sd
 66 sd
 67 sd
 68 sd
 69 sd
 70 sd
 71 sd
128 sd
129 sd
130 sd
131 sd
132 sd
133 sd
134 sd
135 sd
179 mmc
254 ubiblock
259 blkext

But at this time we have not added any operations to this character device. Let's continue to see how to add operations.

Character device initialization

Above we applied to the kernel to register a character device number, but we have not yet established a relationship with the device number. So, how is it established?

static dev_t            dao_devt;	// 上面注册得到的主设备号
static struct cdev      dao_cdev;	// 字符设备结构体
static const struct file_operations dao_fops = {
    
    	// 字符设备操作集
}

cdev_init(&dao_cdev, &dao_fops);	// 初始化字符设备结构体,并更新其文件字符操作集
cdev_add(&dao_cdev, dao_devt, MINORMASK + 1);	// 将主设备号devt与字符设备 cdev 建立关系,实际上就是更新cdev中的主设备号成员信息。同时在cdev_add中,cdev信息保存到指针数组cdev_map中。

struct file_operations

What does file_operations do? Any device in Linux is a file. For files, we have various operations, such as opening, reading, writing, closing, etc. The above operations will be different for different devices, so the device driver needs to be customized. A good set of operations for the device allows the application layer to use the device correctly.

After the above operations, our character device has completed basic initialization. /proc/devicesYou can see the registered character device number in , and the file_operations for the device operation have also been filled. At this time, the user space can already perform operations on the device. operate.

If you complete the above operations, you will find that there is no relevant character node in the /dev directory. At this time, you can only mknod /dev/dao c 251 0create /dev/daothe node through such a command. When executed, cat /dev/daoyou will see that the open, read, and release functions of file_operations are called in sequence.

So what do we need to do to complete the node registration in the /dev directory?

Create device node

Before introducing the creation of nodes, let's first understand classes. In the kernel, you often see xxx_class. The kernel divides devices into character devices, block devices, network devices, and also into classes. Devices with the same characteristics belong to the same class. You can create a class yourself, and the device will belong to a certain class. Well, above we have completed the initialization of the character device, but we have not completed binding it to a class. Therefore, to create a device node, we first create a class:

#define DAO_CLASS_NAME  "dao"
static struct class     *dao_class;

/* 创建一个class,此时在/sys/class目录下看到一个叫dao的文件夹,这个就是我们注册的class */
dao_class = class_create(THIS_MODULE, DAO_CLASS_NAME); 

Then create the device:

static struct device    *dao_dev;

dao_dev = device_create(dao_class, NULL, dao_devt, NULL, DAO_NAME);

函数声明:
struct device *device_create(struct class *class, struct device *parent,
                             dev_t devt, void *drvdata, const char *fmt, ...);

Through device_create, we bind the character device number dao_devt to dao_class and create a device. Through the device_create function declaration, we can know that when creating a device, the class, parent device, device number, device private data, and device name can be passed in.

device_create mainly completes the following operations:

  • Complete struct devicethe initialization of the structure;
  • Notify other buses of the platform that new devices have been added to the system;
  • Create device uevent node;
  • Establish a link between the device and class;
  • Add other device node information;
  • If bus, add bus;
  • Create device attr_dev;
  • Create device nodes in the sys directory;
  • Create device nodes in the dev directory;
  • Trigger the probe of the bus;

After completing the above operations, the device completes the corresponding registration, and the user space can operate the device normally.

Framework architecture diagram

Insert image description here

routine

// SPDX-License-Identifier: GPL-2.0+
/*
 * dao char device test code
 *
 * Copyright (c) 2022, dao. All rights reserved.
 */

#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/cdev.h>
#include <linux/sysfs.h>
#include <linux/fs.h>


#define DAO_NAME	"dao"
#define DAO_CLASS_NAME	"dao"

static dev_t		dao_devt;
static struct class	*dao_class;
static struct cdev	dao_cdev;
static struct device	*dao_dev;

static int dao_dev_open(struct inode *inode, struct file *file)
{
    
    
	pr_info("%s\n", __func__);

	return 0;
}

static int dao_dev_release(struct inode *inode, struct file *file)
{
    
    
	pr_info("%s\n", __func__);

	return 0;
}

static ssize_t dao_dev_read(struct file *file, char __user *buf, size_t count, loff_t *ptr)
{
    
    
	pr_info("%s\n", __func__);

	return 0;
}

static long dao_dev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    
    
        pr_info("%s\n", __func__);

        return 0;
}

static const struct file_operations dao_fops = {
    
    
        .owner          = THIS_MODULE,
        .open           = dao_dev_open,
        .release        = dao_dev_release,
        .read           = dao_dev_read,
        .unlocked_ioctl = dao_dev_ioctl,
};

static int __init dao_dev_init(void)
{
    
    
	int ret = 0;

	ret = alloc_chrdev_region(&dao_devt, 0, MINORMASK + 1, DAO_NAME);
	if (ret < 0) {
    
    
		pr_err("Error: failed to register dao_dev, err: %d\n", ret);
		return ret;
	}
	cdev_init(&dao_cdev, &dao_fops);
	cdev_add(&dao_cdev, dao_devt, MINORMASK + 1);
	pr_info("%s: major %d\n", __func__, MAJOR(dao_devt));

	dao_class = class_create(THIS_MODULE, DAO_CLASS_NAME);
	if (IS_ERR(dao_class)) {
    
    
		pr_err("Error: failed to register dao_dev class\n");
		ret = PTR_ERR(dao_class);
		goto failed1;
	}

	dao_dev = device_create(dao_class, NULL, dao_devt, NULL, DAO_NAME);
	if (!dao_dev)
		goto failed2;

	return 0;

failed2:
	class_destroy(dao_class);
failed1:
	cdev_del(&dao_cdev);
	unregister_chrdev_region(dao_devt, MINORMASK + 1);

	return ret;
}

static void __exit dao_dev_exit(void)
{
    
    
	device_destroy(dao_class, dao_devt);
	class_destroy(dao_class);
	cdev_del(&dao_cdev);
	unregister_chrdev_region(dao_devt, MINORMASK + 1);
}

module_init(dao_dev_init)
module_exit(dao_dev_exit)

Driver registration

The device registration is introduced above, but what is the driver registration process? Let’s introduce it below. Driver registration is completed through the driver_register() function, which mainly does the following things:

  1. Drivers are all mounted on the bus, and drivers are also distinguished by name, so you need to confirm that there is no driver with the same name on the bus;
  2. Mount the driver on the bus, add the driver to the klist_drivers linked list, and then perform a matching operation with the device;
  3. Create a driver attribute configuration node;

The specific device and driver matching operation will be introduced in the next chapter.

Guess you like

Origin blog.csdn.net/weixin_41944449/article/details/132922704