Detailed explanation of Linux character device driver II (using device driver model)

Posted by Mr Tech on Wed, 24 Nov 2021 21:49:02 +0100

preface

Please read: Detailed explanation of Linux character device driver
This article mainly comes from punctual atom, wildfire Linux tutorial and my understanding. If there is infringement, please contact me in time to delete it.

text

Why do I need a device driven model

  • The early kernel (before 2.4) did not have a unified device driver model, but it can still be used
  • Use devfs during 2.4 ~ 2.6 and mount it in / dev directory.
    • You need to create a device file (devfs_register) in the kernel driver and name it rigid
  • After 2.6, use sysfs and mount it in the / sys directory
    • Manage the equipment by classification and hierarchy
    • Cooperate with udev/mdev daemon to dynamically create device files, and command rules can be formulated freely

sysfs overview

linux system embodies the device driver model through sysfs

  • sysfs is a virtual file system (similar to the proc file system)
  • The inode node corresponding to the directory will record the basic drive object, so as to form a hierarchy of devices in the system
  • Users can read and write different files in the directory to configure different properties of the kobject

Basic elements of device driven model

  • kobject: a directory in sysfs, which is often used to represent basic driver objects. It is not allowed to send messages to user space
  • kset: a directory in sysfs, which is often used to manage kobject s and allow sending messages to user space
  • kobj_type: the operation interface of the attribute file in the directory

Driving model I

kset can batch manage kobject s
kobject cannot batch manage kobjects

Driving model II


The upper kobject node cannot traverse to find the lower kobject

Usually, we use model 1. By using the driver model, we no longer need to manually call the mknod command to create the device file (node)

kobject

Each directory in sysfs corresponds to a kobject

struct kobject {
	//The name used to represent the kobject
	const char		*name;
	//struct Node 
	struct list_head	entry;
	//This is the upper level node of the kobject to build the hierarchical relationship between kobjects
	struct kobject		*parent;
	//The kset object to which the kobject belongs is used for batch management of kobject objects
	struct kset		*kset;
	//Operations and properties related to the sysfs file system of the Kobject
	struct kobj_type	*ktype;
	//The kobject corresponds to a directory entry in the sysfs file system
	struct kernfs_node	*sd; /* sysfs directory entry */
	//The number of references to this kobject
	struct kref		kref;
#ifdef CONFIG_DEBUG_KOBJECT_RELEASE
	struct delayed_work	release;
#endif
	//Record the initialization status of the kernel object
	unsigned int state_initialized:1;
	//Indicates whether the kernel object represented by the kobject has established a directory in sysfs
	unsigned int state_in_sysfs:1;
	unsigned int state_add_uevent_sent:1;
	unsigned int state_remove_uevent_sent:1;
	unsigned int uevent_suppress:1;
};

kset

struct kset {
	//It is used to build a linked list of object objects
	struct list_head list;
	//Spin lock
	spinlock_t list_lock;
	//The kobject variable of the current kset kernel object
	struct kobject kobj;
	//A set of function pointers are defined. When some kobject objects in kset change state and need to be notified to the user space, the functions in kset are called to complete
	const struct kset_uevent_ops *uevent_ops;
}

kobj_type

struct kobj_type {
	//Called when the kobject object is destroyed
	void (*release)(struct kobject *kobj);
	//kobject object attribute file unified operation interface
	const struct sysfs_ops *sysfs_ops;
	//The name of the default kobject property file, "file specific operation interface"
	struct attribute **default_attrs;                                                         
	const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
	const void *(*namespace)(struct kobject *kobj);
	void (*get_ownership)(struct kobject *kobj, kuid_t *uid, kgid_t *gid);
};

Next, we can complete the construction of the device file system in the driver module

Firstly, from the driver model, we can know that kset, as the container of kobject, reflects the hierarchical relationship of device driver, which is commonly understood as the device type under sys / directory_ create_ and_ The add () function creates a device type directory under the sys / directory

struct kset *kset_create_and_add(const char *name,
const struct kset_uevent_ops *uevent_ops,struct kobject *parent_kobj)
{
	struct kset *kset;
	int error;

	kset = kset_create(name, uevent_ops, parent_kobj);
	if (!kset)
		return NULL;

	error = kset_register(kset);
	if (error) {
		kfree(kset);
		return NULL;
	}
	return kset;
}

Then through kobject_ create_ and_ In the add () function, we build a kobject object, build a directory item (kernfs_node) in sysfs, and associate them. Generally speaking, we create a specific device, namely sys / device type / specific device

struct kobject *kobject_create_and_add(const char *name, struct kobject *parent)
{
	struct kobject *kobj;
	int retval;
	/*Create and initialize a kobject object*/
	kobj = kobject_create();
	if (!kobj)
		return NULL;
	/*sysfs Create a directory entry and associate it with the kobject object*/
	retval = kobject_add(kobj, parent, "%s", name);
	if (retval) {
		pr_warn("%s: kobject_add error: %d\n", __func__, retval);
		kobject_put(kobj);
		kobj = NULL;
	}
	return kobj;
}

Finally, you need to build multiple attribute files for the kobject object, set the specific operation interface for each attribute file, and the inode object of vfs and the kernfs of sysfs_ Binding process of node object.

For this process, we first build multiple kobjs_ Attribute structure, each containing the operation interface of specific attributes. Store the structure pointers of these different attributes in the attribute * type array through sysfs_create_group() function, we can call the array pointer to complete the creation of different attribute files.

int sysfs_create_group(struct kobject *kobj,
		       const struct attribute_group *grp)
{
	return internal_create_group(kobj, 0, grp);
}

kobj_attribute structure

struct kobj_attribute {
	struct attribute attr;
	ssize_t (*show)(struct kobject *kobj, struct kobj_attribute *attr,
			char *buf);
	ssize_t (*store)(struct kobject *kobj, struct kobj_attribute *attr,
			 const char *buf, size_t count);
};

Take the wild fire equipment driver model code as an example

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <asm/io.h>

#define DEV_MAJOR 		 0 		/*  Dynamic application master equipment number*/
#define DEV_NAME 		 "red_led"  	/* LED device name*/

/* GPIO Virtual address pointer */
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO04;
static void __iomem *SW_PAD_GPIO1_IO04;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;

static int foo;

static ssize_t foo_show(struct kobject *kobj, struct kobj_attribute *attr,
			char *buf)
{
	return sprintf(buf, "%d\n", foo);
}

static ssize_t foo_store(struct kobject *kobj, struct kobj_attribute *attr,
			 const char *buf, size_t count)
{
	int ret;

	ret = kstrtoint(buf, 10, &foo);
	if (ret < 0)
		return ret;

	return count;
}

static struct kobj_attribute foo_attribute =
	__ATTR(foo, 0664, foo_show, foo_store);


static ssize_t led_show(struct kobject *kobj, struct kobj_attribute *attr,
		      char *buf)
{
	int var;

	if (strcmp(attr->attr.name, "led") == 0)
			var =123;

	return sprintf(buf, "%d\n", var);
}

static ssize_t led_store(struct kobject *kobj, struct kobj_attribute *attr,
		       const char *buf, size_t count)
{

	if (strcmp(attr->attr.name, "led") == 0){
		if(!memcmp(buf,"on",2)) {	
			iowrite32(0 << 4, GPIO1_DR);	
		} else if(!memcmp(buf,"off",3)) {
			iowrite32(1 << 4, GPIO1_DR);
		}
	}
	return count;
}

static struct kobj_attribute led_attribute =
	__ATTR(led, 0664, led_show, led_store);

static struct attribute *attrs[] = {
	&foo_attribute.attr,
	&led_attribute.attr,
	NULL,	/* need to NULL terminate the list of attributes */
};

static struct attribute_group attr_group = {
	.attrs = attrs,
};

static struct kobject *led_kobj;

static int __init led_init(void)
{
	int retval;

	/* GPIO Correlation register mapping */
  	IMX6U_CCM_CCGR1 = ioremap(0x20c406c, 4);
	SW_MUX_GPIO1_IO04 = ioremap(0x20e006c, 4);
  	SW_PAD_GPIO1_IO04 = ioremap(0x20e02f8, 4);
	GPIO1_GDIR = ioremap(0x0209c004, 4);
	GPIO1_DR = ioremap(0x0209c000, 4);


	/* Enable GPIO1 clock */
	iowrite32(0xffffffff, IMX6U_CCM_CCGR1);

	/* Set GPIO1_IO04 multiplexed to normal GPIO*/
	iowrite32(5, SW_MUX_GPIO1_IO04);
	
    /*Set GPIO properties*/
	iowrite32(0x10B0, SW_PAD_GPIO1_IO04);

	/* Set GPIO1_IO04 is an output function */
	iowrite32(1 << 4, GPIO1_GDIR);

	/* LED Output high level */
	iowrite32(1<< 4, GPIO1_DR);

	/* Create a kobject object*/
	led_kobj = kobject_create_and_add("led_kobject", NULL);
	if (!led_kobj)
		return -ENOMEM;

	/* Set properties file for kobject*/
	retval = sysfs_create_group(led_kobj, &attr_group);
	if (retval)
		kobject_put(led_kobj);

	return retval;

	return 0;
}

static void __exit led_exit(void)
{
	/* Unmap */
	iounmap(IMX6U_CCM_CCGR1);
	iounmap(SW_MUX_GPIO1_IO04);
	iounmap(SW_PAD_GPIO1_IO04);
	iounmap(GPIO1_DR);
	iounmap(GPIO1_GDIR);

	/* Unregister character device driver */
	kobject_put(led_kobj);
}

module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("embedfire ");
MODULE_DESCRIPTION("led_module");
MODULE_ALIAS("led_module");

summary

The process of calling the open function in user space is still the process of binding the operation interface. This completes the writing of character device driver using device driver model.

Topics: Linux Embedded system