[Linux driver] Character device driver framework (1) - module initialization (device number registration, device creation)

There are two ways to drive the operation:

  1. Compile the driver into the Linux kernel, and automatically run the driver when the kernel starts
  2. Compile the driver into a module ( .ko file ), after the kernel starts, use insmod or modprobe to load the driver module

Here we use the second method , which is convenient for debugging. You only need to load the driver without compiling the kernel. After you are sure that there is no problem, compile it to the kernel as needed.


Table of contents

1. Loading/unloading of the driver module

2. Register character device

1. Register device number 

2. Create and load a character device

3. Unregister the device number and uninstall the character device

3. Automatically create character device nodes

1. Create class and device nodes

2. Removal of class directories and device nodes

4. Finishing: module_init, module_exit function template


1. Loading/unloading of the driver module

When the driver module is loaded or unloaded, some initialization or finishing may be required, which requires macros provided by the kernel

  • module_init: When the current module is loaded into the kernel, the function registered by module_init will be called automatically
  • module_exit: When the current module is unloaded from the kernel, the function registered by module_exit will be called automatically
// 使用 __init 修饰
static int __init chrdevbase_init(void)
{
    /* 驱动入口实现 */
	return 0;
}

// 使用 __exit 修饰
static void __exit chrdevbase_exit(void)
{
    /* 驱动出口实现 */
}

module_init(xxx_init);     //注册 “模块入口函数”
module_exit(xxx_exit);     //注册 “模块出口函数”

2. Register character device

Registering a character device is equivalent to telling the kernel that it hopes to help you manage this character device. When an application reads and writes a character device file, it will automatically call the read / write function corresponding to the character device.

In addition to the device number, there are subsequent character device registration and device node creation. For the convenience of management, we put these in a unified structure.

struct chrdev_led_t{
    struct class* class;	/* 设备节点所属类 */
	struct device* device;	/* 设备节点 */

    struct cdev dev;		/* 字符设备 */
	dev_t devid;			/* 设备号 */
	int major;				/* 主设备号 */
	int minor;				/* 次设备号 */
};	
static struct chrdev_led_t chrdev_led;

1. Register device number 

Registering the device number is mainly to determine whether the device number has been registered before

  • If it is not the first time to register, because you already have a major device number, you can directly assign it statically
  • If it is the first registration, the device number is dynamically allocated, and the major, minor, and devid will all get the values ​​at this time
if (chrdev_led.major)
{
    chrdev_led.devid = MKDEV(chrdev_led.major, 0);
    ret = register_chrdev_region(chrdev_led.devid, 1, CHRDEVBASE_NAME);
}
else
{
    ret = alloc_chrdev_region(&chrdev_led.devid, 0, 1, CHRDEVBASE_NAME);
    chrdev_led.major = MAJOR(chrdev_led.devid);
    chrdev_led.minor = MINOR(chrdev_led.devid);
}

Note: Register the device number in module_init and release the device number in module_exit

2. Create and load a character device

We need to load the character device and the device number into the kernel together, and then the character device is registered. Before loading into the kernel, we need to initialize the character device first.

  • Create a character device: struct cdev dev  
  • Initialize character device: cdev_init
  • Load character device to kernel: cdev_add
/*
 * 设备操作函数结构体
 */
static struct file_operations chrdevbase_fops = {
	.owner = THIS_MODULE, 
	.open = chrdevbase_open,
	.read = chrdevbase_read,
	.write = chrdevbase_write,
	.release = chrdevbase_release,
};


/* 2. 初始化字符设备 */
chrdev_led.dev.owner = THIS_MODULE;
cdev_init(&chrdev_led.dev, &chrdevbase_fops);			// 初始化字符设备
/* 3. 将字符设备添加到内核 */
cdev_add(&chrdev_led.dev, chrdev_led.devid, 1);			// 将字符设备添加到内核

Note: Load the character device in module_init and unload the character device in module_exit

3. Unregister the device number and uninstall the character device

When the driver is uninstalled, of course we also need to do some finishing work

/* 注销字符设备 */
unregister_chrdev_region(chrdev_led.devid, 1);		// 注销设备号
cdev_del(&chrdev_led.dev);							// 卸载字符设备

3. Automatically create character device nodes

For the functions involved in this part, please refer to: Automatically create/delete device nodes

1. Create class and device nodes

After the device driver is loaded into the kernel, the device file will not be generated in the /dev directory, so you need to implement the logic of creating the device file yourself. Creating a device file consists of two parts:

  • Create the class to which the device belongs. The corresponding class directory will be generated in the /sys/class directory
  • Create a device file. Device files will be generated under /dev, and device directories will be generated under /sys/class/class directory/
/* 自动创建设备节点 */
// 设备节点所属类
chrdev_led.class = class_create(THIS_MODULE, CHRDEVBASE_NAME);
if (IS_ERR(chrdev_led.class))
{
    goto node_create_err;
}
// 创建设备节点
chrdev_led.device = device_create(chrdev_led.class, NULL, chrdev_led.devid, NULL, CHRDEVBASE_NAME);
if (IS_ERR(chrdev_led.device))
{
    goto node_create_err;
}

2. Removal of class directories and device nodes

When uninstalling the driver, the device directory and device node must be removed together, and the order of deletion is reversed from the order of creation:

  • Delete the device file first
  • Then remove the class directory
device_destroy(chrdev_led.class, chrdev_led.devid);	// 删除节点
class_destroy(chrdev_led.class);					// 删除类

4. Finishing: module_init, module_exit function template

#define CHRDEVBASE_NAME "chrdevbase" 	/* 设备名 */

struct chrdev_led_t{
	struct class* class;	/* 设备节点所属类 */
	struct device* device;	/* 设备节点 */

	struct cdev dev;		/* 字符设备 */
	dev_t devid;			/* 设备号 */
	int major;				/* 主设备号 */
	int minor;				/* 次设备号 */
};	
static struct chrdev_led_t chrdev_led;


/*
 * 设备操作函数结构体
 */
static struct file_operations chrdevbase_fops = {
	.owner = THIS_MODULE, 
	.open = chrdevbase_open,
	.read = chrdevbase_read,
	.write = chrdevbase_write,
	.release = chrdevbase_release,
};

/*
 * @description	: 驱动入口函数 
 * @param 		: 无
 * @return 		: 0 成功;其他 失败
 */
static int __init chrdevbase_init(void)
{
	int ret = 0;

	/* 1. 注册设备号 */
	if (chrdev_led.major)
	{
		chrdev_led.devid = MKDEV(chrdev_led.major, 0);
		ret = register_chrdev_region(chrdev_led.devid, 1, CHRDEVBASE_NAME);
	}
	else
	{
		ret = alloc_chrdev_region(&chrdev_led.devid, 0, 1, CHRDEVBASE_NAME);
		chrdev_led.major = MAJOR(chrdev_led.devid);
		chrdev_led.minor = MINOR(chrdev_led.devid);
	}

	/* 2. 初始化字符设备 */
	chrdev_led.dev.owner = THIS_MODULE;
	cdev_init(&chrdev_led.dev, &chrdevbase_fops);					// 初始化字符设备
	/* 3. 将字符设备添加到内核 */
	cdev_add(&chrdev_led.dev, chrdev_led.devid, 1);			// 将字符设备添加到内核

	/* 自动创建设备节点 */
	// 设备节点所属类
 	chrdev_led.class = class_create(THIS_MODULE, CHRDEVBASE_NAME);
	if (IS_ERR(chrdev_led.class))
	{
		return PTR_ERR(chrdev_led.class);
	}
	// 创建设备节点
	chrdev_led.device = device_create(chrdev_led.class, NULL, chrdev_led.devid, NULL, CHRDEVBASE_NAME);
	if (IS_ERR(chrdev_led.device))
	{
		return PTR_ERR(chrdev_led.device);
	}
	
	printk("chrdevbase init!\n");
	return 0;
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit chrdevbase_exit(void)
{	
	/* 注销字符设备 */
	unregister_chrdev_region(chrdev_led.devid, 1);		// 注销设备号
	cdev_del(&chrdev_led.dev);							// 卸载字符设备
	
	device_destroy(chrdev_led.class, chrdev_led.devid);	// 删除节点
	class_destroy(chrdev_led.class);					// 删除类
	printk("chrdevbase exit!\n");
}

/* 
 * 将上面两个函数指定为驱动的入口和出口函数 
 */
module_init(chrdevbase_init);
module_exit(chrdevbase_exit);

/* 
 * LICENSE和作者信息
 */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("author_name");

Guess you like

Origin blog.csdn.net/challenglistic/article/details/131879587