《Linux驱动:register_chrdev、alloc_chrdev_region、register_chrdev_region》

I. Introduction

The Linux kernel provides multiple interfaces for registering character device drivers. The register_chrdev interface is generally recommended not to be used after Linux 2.6. It is recommended to use alloc_chrdev_region or register_chrdev_region. Here we analyze the realization of the three, and summarize the similarities and differences of the three.

二,__register_chrdev_region

register_chrdev, alloc_chrdev_region, and register_chrdev_region will call the __register_chrdev_region function internally. It is necessary to analyze this function first.

2.1 Major and minor device numbers of character devices

The device number of a character device is composed of a major device number + a minor device number, and the major device number and the minor device number are composed of a device number through the MKDEV macro. If you know the device number, you can get the major device number through the MAJOR macro, and the minor device number through the MINOR macro. The kernel finds the corresponding driver through the major device number and minor device number of the device node, that is, the function interface in the file_operations structure.

#define MINORBITS	20
#define MINORMASK	((1U << MINORBITS) - 1)

#define MAJOR(dev)	((unsigned int) ((dev) >> MINORBITS))  // 取dev的高20位,得到设备号中主设备号
#define MINOR(dev)	((unsigned int) ((dev) & MINORMASK))   // 取dev的低12位,得到设备号中次设备号
#define MKDEV(ma,mi)	(((ma) << MINORBITS) | (mi))	   // 通过major、minor组合得到一个dev

2.2 Function Prototype

A character device structure is registered with the kernel, and the starting value and number of minor device numbers of the device are specified.

/*
 * Register a single major with a specified minor range.
 *
 * @major:     主设备号
 * @baseminor: 次设备号起始值
 * @minorct:   该主设备号下次设备号个数
 * @name: 	   设备名称
 * 如果major为0,则由内核自动分配一个主设备号。
 * major大于0,则内核以major为主设备号。
 * 注册成功返回一个struct char_device_struct *,失败返回错误值
 */
static struct char_device_struct *
__register_chrdev_region(unsigned int major, unsigned int baseminor,
			   int minorct, const char *name)

2.3 Function internal implementation

static struct char_device_struct *
__register_chrdev_region(unsigned int major, unsigned int baseminor,
			   int minorct, const char *name)
{
    
    
	struct char_device_struct *cd, **cp;
	int ret = 0;
	int i;

	cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);
	if (cd == NULL)
		return ERR_PTR(-ENOMEM);

	mutex_lock(&chrdevs_lock);

	// 主设备号为0,从chrdevs数组中取出一个空元素对应的索引作为主设备号
	/* temporary */
	if (major == 0) {
    
    
		for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) {
    
    
			if (chrdevs[i] == NULL)
				break;
		}

		if (i == 0) {
    
    
			ret = -EBUSY;
			goto out;
		}
		major = i;
		ret = major;
	}

	cd->major = major;
	cd->baseminor = baseminor;
	cd->minorct = minorct;
	strncpy(cd->name,name, 64);

	// 主设备号与255取余,获取一个chrdevs的索引值
	i = major_to_index(major);

	for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)
		if ((*cp)->major > major ||
		    ((*cp)->major == major &&
		     (((*cp)->baseminor >= baseminor) ||
		      ((*cp)->baseminor + (*cp)->minorct > baseminor))))
			break;
	// 判断当前设备的次设备号是否已经使用
	/* Check for overlapping minor ranges.  */
	if (*cp && (*cp)->major == major) {
    
    
		int old_min = (*cp)->baseminor;
		int old_max = (*cp)->baseminor + (*cp)->minorct - 1;
		int new_min = baseminor;
		int new_max = baseminor + minorct - 1;

		/* New driver overlaps from the left.  */
		if (new_max >= old_min && new_max <= old_max) {
    
    
			ret = -EBUSY;
			goto out;
		}

		/* New driver overlaps from the right.  */
		if (new_min <= old_max && new_min >= old_min) {
    
    
			ret = -EBUSY;
			goto out;
		}
	}

	// 将当前的字符设备结构插入到chrdevs链表中
	cd->next = *cp;
	*cp = cd;
	mutex_unlock(&chrdevs_lock);
	return cd;
out:
	mutex_unlock(&chrdevs_lock);
	kfree(cd);
	return ERR_PTR(ret);
}

Three, register_chrdev

3.1 Function prototype

Register a character device with the kernel. The major device number of the device is major (or assigned by the kernel), and the minor device number is 0 ~ 255. Indicates that major is the major device number, and devices with 0 to 255 as the minor device number correspond to the same device driver.

/**
 * @major: 主设备号
 * @name: 设备名称
 * @fops: file_operations 结构体
 *
 * 如果major为0,则由内核自动分配一个主设备号,此时返回值即为主设备号。
 *
 * major大于0,则内核以major为主设备号,此时注册成功返回0。
 * major大于0,即手动指定主设备号时,必须使用未使用过的主设备号,可以事先查看哪些未使用的主设备号
 * cat /proc/devices 查看。
 */
int register_chrdev(unsigned int major, const char *name,
                    const struct file_operations *fops)

3.2 Function internal implementation

// int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)
register_chrdev(I2C_MAJOR, "i2c", &i2cdev_fops) ->  // i2c通用设备驱动中注册的字符设备,上一篇i2c设备驱动中有分析
    /* static struct char_device_struct *__register_chrdev_region(unsigned int major,
               unsigned int baseminor,
			   int minorct, const char *name) */
    cd = __register_chrdev_region(major, 0, 256, name);
        	cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);
        	cd->major = major;
        	cd->baseminor = baseminor;
        	cd->minorct = minorct;
        	strncpy(cd->name,name, 64);
        	cp = &chrdevs[i];
        	cd->next = *cp;
			*cp = cd;
	cdev = cdev_alloc();  // 在这里申请一个struct cdev数据结构
	cdev->owner = fops->owner;
	cdev->ops = fops;
	err = cdev_add(cdev, MKDEV(cd->major, 0), 256);
	cd->cdev = cdev;

Four, alloc_chrdev_region

4.1 Function Prototype

/**
 * 注册一个主设备号由内核动态分配,次设备号为baseminor~baseminor+count的设备驱动
 * @dev: 用来获取设备号
 * @baseminor:次设备号起始值
 * @count: 次设备号个数
 * @name: 设备名称
 *
 * 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)

4.2 Function Internal Implementation

// int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name)
alloc_chrdev_region(&rtc_devt, 0, RTC_DEV_MAX, "rtc") ->  // 以RTC驱动为例
    // __register_chrdev_region(unsigned int major, unsigned int baseminor,int minorct, const char *name)
	cd = __register_chrdev_region(0, baseminor, count, name) ->
            cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);
	*dev = MKDEV(cd->major, cd->baseminor);   // 即rtc_devt = MKDEV(cd->major, cd->baseminor);

五,register_chrdev_region

5.1 Function Prototype

/**
 * @from: 设备号,一般在调用之前就得通过一个主设备号和次设备号得到一个设备号
 *	比如from = MKDEV(major, 0); 0为次设备号的起始值
 * @count: 次设备个数  以major为主设备号,以0~0+count的次设备号
 * @name: 设备名称.
 *
 * Return value is zero on success, a negative error code on failure.
 */
int register_chrdev_region(dev_t from, unsigned count, const char *name)

5.2 Function Internal Implementation

int register_chrdev_region(dev_t from, unsigned count, const char *name)
{
    
    
	struct char_device_struct *cd;
	dev_t to = from + count;
	dev_t n, next;

	for (n = from; n < to; n = next) {
    
    
		next = MKDEV(MAJOR(n)+1, 0);
		if (next > to)
			next = to;
		cd = __register_chrdev_region(MAJOR(n), MINOR(n),
			       next - n, name);
		if (IS_ERR(cd))
			goto fail;
	}
	return 0;
fail:
	to = n;
	for (n = from; n < to; n = next) {
    
    
		next = MKDEV(MAJOR(n)+1, 0);
		kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
	}
	return PTR_ERR(cd);
}

6. An example of registering character devices with three interfaces

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <linux/poll.h>
#include <linux/cdev.h>

/* 1. 确定主设备号 */
static int major;

static int hello_open(struct inode *inode, struct file *file)
{
    
    
	printk("hello_open\n");
	return 0;
}

static int hello2_open(struct inode *inode, struct file *file)
{
    
    
	printk("hello2_open\n");
	return 0;
}


/* 2. 构造file_operations */
static struct file_operations hello_fops = {
    
    
	.owner = THIS_MODULE,
	.open  = hello_open,
};

static struct file_operations hello2_fops = {
    
    
	.owner = THIS_MODULE,
	.open  = hello2_open,
};


#define HELLO_CNT   2

static struct cdev hello_cdev;
static struct cdev hello2_cdev;
static struct class *cls;

static int hello_init(void)
{
    
    
	dev_t devid;
	
	/* 3. 告诉内核 */
#if 0
	major = register_chrdev(0, "hello", &hello_fops); /* (major,  0), (major, 1), ..., (major, 255)都对应hello_fops */
#else
	if (major) {
    
    
		// 事先知道可用的主设备号,和起始次设备号
		devid = MKDEV(major, 0);
		register_chrdev_region(devid, HELLO_CNT, "hello");  /* (major,0~1) 对应 hello_fops, (major, 2~255)都不对应hello_fops */
	} else {
    
    
		// 事先不知道可用的主设备号,由内核动态分配
		alloc_chrdev_region(&devid, 0, HELLO_CNT, "hello"); /* (major,0~1) 对应 hello_fops, (major, 2~255)都不对应hello_fops */
		major = MAJOR(devid);                     
	}
	
	cdev_init(&hello_cdev, &hello_fops);
	cdev_add(&hello_cdev, devid, HELLO_CNT);

	devid = MKDEV(major, 2);
	register_chrdev_region(devid, 1, "hello2");
	cdev_init(&hello2_cdev, &hello2_fops);
	cdev_add(&hello2_cdev, devid, 1);
	
#endif

	cls = class_create(THIS_MODULE, "hello");
	// 使用register_chrdev注册,下面的四个设备节点都将对应该设备驱动,都能调用hello_open
	// 使用 register_chrdev_region/alloc_chrdev_region (60/63)注册,设备节点/dev/hello0、/dev/hello1对应hello_fops设备驱动,调用hello_open打开
	// 使用 register_chrdev_region/alloc_chrdev_region (71)注册,设备节点/dev/hello2对应hello2_fops设备驱动,调用hello2_open打开
	// /dev/hello3节点未注册到设备驱动,无法打开设备。
	class_device_create(cls, NULL, MKDEV(major, 0), NULL, "hello0"); /* /dev/hello0 */
	class_device_create(cls, NULL, MKDEV(major, 1), NULL, "hello1"); /* /dev/hello1 */
	class_device_create(cls, NULL, MKDEV(major, 2), NULL, "hello2"); /* /dev/hello2 */
	class_device_create(cls, NULL, MKDEV(major, 3), NULL, "hello3"); /* /dev/hello3 */
	
	
	return 0;
}

static void hello_exit(void)
{
    
    
	class_device_destroy(cls, MKDEV(major, 0));
	class_device_destroy(cls, MKDEV(major, 1));
	class_device_destroy(cls, MKDEV(major, 2));
	class_device_destroy(cls, MKDEV(major, 3));
	class_destroy(cls);

	cdev_del(&hello_cdev);
	unregister_chrdev_region(MKDEV(major, 0), HELLO_CNT);

	cdev_del(&hello2_cdev);
	unregister_chrdev_region(MKDEV(major, 2), 1);
}

module_init(hello_init);
module_exit(hello_exit);


MODULE_LICENSE("GPL");


Seven, summary

  • Use register_chrdev to register character devices. By default, 0~255 secondary devices under the main device correspond to the same device driver (using the same interface of the file_operations structure).
  • Use register_chrdev_region/alloc_chrdev_region to register character devices, and you can specify how many minor device numbers under the major device number correspond to the same device driver (using the same interface of the file_operations structure).
  • Use register_chrdev to register character devices, internally apply for struct cdev structure, and call cdev_add function to add devices.
  • To register a character device using register_chrdev_region/alloc_chrdev_region, you need to define the struct cdev structure externally in advance, then use the function cdev_init to initialize it, and finally call the cdev_add function externally to add the device.
  • The difference between register_chrdev_region and alloc_chrdev_region is that the former knows an available major device number in advance, and the latter is dynamically assigned a major device number by the kernel.

Guess you like

Origin blog.csdn.net/qq_40709487/article/details/127463973