Understand in one article |Linux character device driver

img
My circle: Senior engineer gathering place
I am Brother Dong, a senior embedded software development engineer. I am engaged in embedded Linux driver development and system development. I have worked for a Fortune 500 company!
Creative philosophy: focus on sharing high-quality embedded articles so that everyone can gain something from reading!
img


image-20231123091238538

1 Introduction

As we all know,Linuxthe kernel mainly includes three driver models, character device driver, block device driver and network device driver.

Among them, Linux character device driver can be said to be the most common driver model in Linux driver development.

Our series of articles is mainly to help you get started quicklyLinuxDriver development, this article mainly focuses on understanding some character device driver frameworks and mechanisms.

Series of articles based onKernel 4.19

 

2. Key data structures

2.1 cdev

struct cdev {
    
    
    struct kobject kobj;
    struct module *owner;
    const struct file_operations *ops;
    struct list_head list;
    dev_t dev;
    unsigned int count;
} __randomize_layout;

Structure name:cdev

Text position:include/linux/cdev.h

Main function: cdev can be understood as char device, used to abstract a character device.

Core members and meaning:

  • kobj: Represents a kernel object.
  • owner: Pointer to the module
  • ops: Pointer to file operation, including open, read, write and other operation interfaces
  • list: Used to add the device to the kernel module linked list
  • dev: Device number, consisting of major device number and minor device number
  • count: Indicates how many devices of the same type there are, and also indirectly indicates the range of device numbers.
  • __randomize_layout: A compiler directive that randomizes the layout of a structure for added safety.

 

2.2 file_operations

struct file_operations {
    
    
    struct module *owner;
    loff_t (*llseek) (struct file *, loff_t, int);
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
    ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
    ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
    int (*iterate) (struct file *, struct dir_context *);
    int (*iterate_shared) (struct file *, struct dir_context *);
    __poll_t (*poll) (struct file *, struct poll_table_struct *);
    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
    long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
    int (*mmap) (struct file *, struct vm_area_struct *);
    unsigned long mmap_supported_flags;
    int (*open) (struct inode *, struct file *);
    int (*flush) (struct file *, fl_owner_t id);
    int (*release) (struct inode *, struct file *);
    int (*fsync) (struct file *, loff_t, loff_t, int datasync);
    int (*fasync) (int, struct file *, int);
    int (*lock) (struct file *, int, struct file_lock *);
    ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
    unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
    int (*check_flags)(int);
    int (*flock) (struct file *, int, struct file_lock *);
    ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
    ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
    int (*setlease)(struct file *, long, struct file_lock **, void **);
    long (*fallocate)(struct file *file, int mode, loff_t offset,
              loff_t len);
    void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
    unsigned (*mmap_capabilities)(struct file *);
#endif
    ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,
            loff_t, size_t, unsigned int);
    int (*clone_file_range)(struct file *, loff_t, struct file *, loff_t,
            u64);
    int (*dedupe_file_range)(struct file *, loff_t, struct file *, loff_t,
            u64);
    int (*fadvise)(struct file *, loff_t, loff_t, int);
} __randomize_layout;

Structure name:file_operations

Text position:include/linux/fs.h

Main function: As the name suggests, it is mainly used to describe various interfaces for file operations. LinuxAll ideas of connecting files, the kernel wants to Whichever file is operated needs to be implemented through these interfaces.

Core members and meaning:

  • open:Function to open a file
  • read: Function to read files.
  • write: Function for writing files.
  • release: Function to close the file.
  • flush: Function to refresh the file, usually called when closing the file.
  • llseek: Function that changes the position of the file read and write pointer.
  • fsync: Function to write file data to disk synchronously.
  • poll:Ask whether the file can be read and written non-blockingly

 

2.3 dev_t

typedef u32 __kernel_dev_t;

typedef __kernel_dev_t		dev_t;

Type name:dev_t

Text position:include/linux/types.h

Main function: Indicates the device number corresponding to the character device, including the major device number and the minor device number.

 

3. Relationship between data structures

image-20231123085448145

The above figure is a diagram of the data structure of the character device driver and API,

If you need the original files, you can get them at the public address [Embedded Art].

 

4. Overall architecture of character device driver

4.1 Loading and unloading functions

The first thing the driver implements is the loading and unloading functions, which are also the entry functions of the driver program.

We generally define the driver's loading and unloading functions like this:

static int __init xxx_init(void)
{
    
    

}

static void __exit xxx_exit(void)
{
    
    
    
}

module_init(xxx_init);
module_exit(xxx_exit);

This code is to implement the loading and unloading of a universal driver. For the implementation mechanism of module_init and module_exit, you can check the previous summary article.

 

4.2 Device number management

4.2.1 The concept of device number

Each type of character device has a unique device number, and the device number is divided into a major device number and a minor device number. So what are the functions of these two?

  • Major device number: used to identify the type of device,
  • Minor device number: used to distinguish different devices of the same type

To put it simply, the major device number is used to distinguish whether it is a IIC device or a SPI device, while the minor device number is used to distinguish < Under the a i=3> device, which device is it? or . IICMPU6050EEPROM

 

4.2.2 Assignment of device numbers

Understand the concept of device numbers. There are so many device numbers in Linux, so how do we use the correct device number?

There are two ways to allocate device numbers, one is dynamic allocation and the other is static allocation. It can also be understood as one is automatic allocation by the kernel and the other is manual allocation.

Static allocation function:

int register_chrdev_region(dev_t from, unsigned count, const char *name);
  • from: Indicates a known device number
  • count: Indicates the number of consecutive device numbers (how many devices of the same type are there)
  • name: Indicates the name of the device or driver

Function effect: Starting with from device number, continuously allocate count device numbers of the same type a>

 

Dynamic allocation function:

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
  • dev: Pointer to the device number, used to store the value of the assigned device number
  • baseminor: Starting value for allocation of minor device number
  • count: Indicates the number of consecutive device numbers (how many devices of the same type are there)
  • name: Indicates the name of the device or driver

Function effect: Starting from the baseminor secondary device number, continuously allocate count device numbers of the same type. And automatically assign a major device number, and assign the device number information composed of major and minor to*dev

 

The biggest difference between these two functions is:

  • register_chrdev_region: Before calling, the major device number and minor device number have been predefined. After calling this interface, the customized device number will be registered and added to the subsystem to facilitate the system to track the usage of the system device number.
  • alloc_chrdev_region: Before calling, the major device number and minor device number are not defined; after calling, the major device number is represented by 0 to be automatically assigned, and the automatically assigned device number will be It is also added to the subsystem to facilitate the system to track the usage of system device numbers.

 

What these two functions have in common is:

The system maintains an array list to register all used device number information. In the final analysis, these two interfaces also register their device number information into the device number list maintained by the system to avoid subsequent conflicting use.

InLinux, we can use the cat /proc/devices command to view the list of all device numbers registered by i.

 

When we have time later, we can talk in detail about the automatic allocation mechanism and management mechanism of device numbers.

 

4.2.3 Cancellation of device number

As a system resource, the device number must be returned to the system when the corresponding device is uninstalled. Regardless of whether it is allocated statically or dynamically, the following function will eventually be called to log out.

void unregister_chrdev_region(dev_t from, unsigned count);
  • from: Indicates a known device number
  • count: Indicates the number of consecutive device numbers (how many devices of the same type are there)

Function effect: To log outfromconsecutive devices under the major device numbercount

 

4.2.4 Obtaining device number

The management of device number is very simple. In the key data structure, we see that the type of device number is dev_t, which is represented by the type u32 A numerical value.

The dividing line between the major device number and the minor device number is specified byMINORBITS macro definition:

#define MINORBITS	20

That is, the occupation of the major device number is high12bit, and the occupation of the minor device number is low20bit

Moreover, the kernel also provides relatedAPI interfaces to obtain the major device number and minor device number, as well as the interface to generate the device number, as follows:

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

#define MAJOR(dev)	((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev)	((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi)	(((ma) << MINORBITS) | (mi))

In the above, the primary and secondary device numbers are obtained through the shift operation.

 

4.2.4 Common code implementation

#define CUSTOM_DEVICE_NUM 0
#define DEVICE_NUM 1
#device DEVICE_NAME "XXXXXX"
static dev_t global_custom_major = CUSTOM_DEVICE_NUM;

static int __init xxx_init(void)
{
    
    
    dev_t custom_device_number= MKDEV(global_custom_major, 0);	//	custom device number
    /* device number register*/
    if (global_custom_major) {
    
    
        ret = register_chrdev_region(custom_device_number, DEVICE_NUM, DEVICE_NAME);
    } else {
    
    
        ret = alloc_chrdev_region(&custom_device_number, 0, DEVICE_NUM, DEVICE_NAME);
        global_custom_major = MAJOR(custom_device_number);
    }
}

static void __exit xxx_exit(void)
{
    
    
    unregister_chrdev_region(MKDEV(global_mem_major, 0), DEVICE_NUM);
}

module_init(xxx_init);
module_exit(xxx_exit);

This function implements the allocation of device numbers. If the main device number is0, dynamic allocation is used, otherwise static allocation is used.

 

More useful information can be found at:A gathering place for senior engineers to help everyone reach the next level!

 

4.3 Management of character devices

After understanding the management of device numbers, let's take a look at how character devices are managed.

4.3.1. Character device initialization

void cdev_init(struct cdev *cdev, const struct file_operations *fops);
  • cdev: A character device object, which is the character device we created
  • fops: The file processing interface of the character device

Function function: Initialize a character device and bind the corresponding file processing pointer to the character device.

 

4.3.2. Character device registration

int cdev_add(struct cdev *p, dev_t dev, unsigned count);
  • p: A character device pointer, only the character device object to be added
  • dev: The first device number responsible for this character device
  • count: The number of devices of this type

Function function: Add a character device driver toLinux the system.

 

4.3.3. Character device logout

void cdev_del(struct cdev *p);
  • p: Pointer to character device object

Function: Remove the character device driver from the system

 

4.4 Implementation of file operation interface

Because inLinux, everything is a file, so each character device also has a file node corresponding to it.

When we initialize the character device, we will bind the object of struct file_operations to the character device, and its function is to process the character deviceopen, read, write and other operations.

What we have to do is to implement the functional interface we need, such as:

static const struct file_operations global_mem_fops = {
    
    
    .owner = THIS_MODULE,
    .llseek = global_mem_llseek,
    .read = global_mem_read,
    .write = global_mem_write,
    .unlocked_ioctl = global_mem_ioctl,
    .open = global_mem_open,
    .release = global_mem_release,
};

At this point, we have a basic understanding of the framework of a basic character device driver.

 

5. Summary

This article aims to explain in an easy-to-understand manner:

  • Character device driver related data structures
  • data structure diagram
  • core APIintersection
  • Character device driver overall framework

I hope to be helpful.

img
Welcome to follow the official account & Planet [Embedded Art], original by Brother Dong!

Guess you like

Origin blog.csdn.net/dong__ge/article/details/134703452