Linux device driver (2)--Character device driver framework

Code learning materials come from:

Lecture 3.5 My first Linux driver - perfect chrdevbase driver_哔哩哔哩_bilibili

Only for personal study/review, intrusion deleted

1. Introduction to character device drivers

The character device is the most basic type of device driver in the linux driver. The character device is a device that reads and writes one by one byte according to the byte stream. The read and write data are in sequential order. For example, our most common lighting, buttons, i2c, spi, lcd, etc. are all character devices, and the drivers of these devices are called character device drivers.

There is a structure called file_operations in the linux kernel file include/linux/fs.h , which is a collection of linux kernel driver operation functions . We use whatever we need. Commonly used are open, write, read, release, poll, mmap, etc.

open: open the file device

read: read file device

poll: Polling function, used to query whether the device can perform non-blocking read and write

write: write to the device file

release: Release (close) the file device, corresponding to close in the application

mmap: Map the memory of the device to the process space (that is, the user space). Generally, frame buffer devices will use this function, such as the video memory of the LCD driver. After the frame buffer is mapped to the user space, the application can directly operate the video memory. , so that you don't have to copy back and forth between user space and kernel space.

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;

2. Loading and unloading of kernel modules

The linux driver can be compiled into the kernel, that is, zImage, or it can be compiled into a module .ko. When testing, you only need to load the .ko file.

The __init modified function indicates that this function is only used in the initialization phase, and the memory resources occupied by it will be released after use

There are two ways for the module code, one is to statically compile and link into the kernel, and initialize it during system startup; the other is to compile it into a module that can be dynamically loaded, and relocate it to the kernel through insmod dynamic loading. These two ways can be selected by obj-y or obj-m option in Makefile

You can use obj-y when debugging, and use obj-m for specification when submitting code.

Two macro references about module_init and module_exit:

Linux kernel module analysis (module_init macro)_Akimi East's blog-CSDN blog_module_init

Load the driver module: insmod drv.ko or modprobe drv.ko

The difference between insmod and modprobe:

The insmod command cannot solve the module dependency problem. For example, if drv.ko depends on the first.ko module, you must first insmod the first.ko module, while modprobe is smarter and will go to the specified path to find dependencies

Uninstall the driver module: rmmod drv.ko or modprobe -r drv.ko

Check the driver module: lsmod

3. Kernel print function

The kernel printing function is printk, and there are eight levels of printk, for details, please refer to include/linux/kern_levels.h

#define KERN_EMERG      KERN_SOH "0"    /* system is unusable */
#define KERN_ALERT      KERN_SOH "1"    /* action must be taken immediately */
#define KERN_CRIT       KERN_SOH "2"    /* critical conditions */
#define KERN_ERR        KERN_SOH "3"    /* error conditions */
#define KERN_WARNING    KERN_SOH "4"    /* warning conditions */
#define KERN_NOTICE     KERN_SOH "5"    /* normal but significant condition */
#define KERN_INFO       KERN_SOH "6"    /* informational */
#define KERN_DEBUG      KERN_SOH "7"    /* debug-level messages */

0 has the highest priority, and 7 has the lowest priority. If you do not set the message level, printk will choose the default level default_message_loglevel, which is 7.

The level printed by the system kernel can be set by command:

echo 1       4       1      7 > /proc/sys/kernel/printk

There are also some wrapper functions for printk:

pr_xxx

In addition to directly using printk to add message levels, interfaces such as pr_notice, pr_info, pr_warn, and pr_err are also defined in <linux/printk.h>. Using these pr_xxx interfaces, you can save yourself the trouble of specifying message levels.

#define pr_emerg(fmt, ...)     printk(KERN_EMERG pr_fmt(fmt), ##__VA_ARGS__)
#define pr_alert(fmt, ...)     printk(KERN_ALERT pr_fmt(fmt), ##__VA_ARGS__)
#define pr_crit(fmt, ...)      printk(KERN_CRIT pr_fmt(fmt), ##__VA_ARGS__)
#define pr_err(fmt, ...)       printk(KERN_ERR pr_fmt(fmt), ##__VA_ARGS__)
#define pr_warning(fmt, ...)   printk(KERN_WARNING pr_fmt(fmt), ##__VA_ARGS__)
#define pr_warn pr_warning
#define pr_notice(fmt, ...)    printk(KERN_NOTICE pr_fmt(fmt), ##__VA_ARGS__)
#define pr_info(fmt, ...)      printk(KERN_INFO pr_fmt(fmt), ##__VA_ARGS__)

#define pr_devel(fmt, ...)     printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__)
#define pr_debug(fmt, ...)     printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__)

Note that other pr_XXX() functions can print unconditionally, but pr_debug() cannot. Because by default it will not be compiled unless DEBUG is defined or CONFIG_DYNAMIC_DEBUG is set.

dev_xxx

For the driver, some driver model diagnostic macros are also provided in <linux/device.h>, such as dev_err, dev_warn, dev_info and so on. Using them, you can not only print at the marked message level, but also print the corresponding device and driver information, which is very important for driver debugging.

#define dev_emerg(dev, fmt, ...)  _dev_emerg(dev, dev_fmt(fmt), ##__VA_ARGS__)
#define dev_crit(dev, fmt, ...)   _dev_crit(dev, dev_fmt(fmt), ##__VA_ARGS__)
#define dev_alert(dev, fmt, ...)  _dev_alert(dev, dev_fmt(fmt), ##__VA_ARGS__)
#define dev_err(dev, fmt, ...)    _dev_err(dev, dev_fmt(fmt), ##__VA_ARGS__)
#define dev_warn(dev, fmt, ...)   _dev_warn(dev, dev_fmt(fmt), ##__VA_ARGS__)
#define dev_notice(dev, fmt, ...) _dev_notice(dev, dev_fmt(fmt), ##__VA_ARGS__)
#define dev_info(dev, fmt, ...)   _dev_info(dev, dev_fmt(fmt), ##__VA_ARGS__)

4. Character device registration and cancellation

For the character device, the character device needs to be registered after the driver module is successfully loaded, and similarly, the character device needs to be unregistered when the driver module is uninstalled.

// 注册字符设备函数
static inline int register_chrdev(unsigned int major, const char *name,
                                  const struct file_operations *fops)
{
        return __register_chrdev(major, 0, 256, name, fops);
}

// 注销字符设备函数
static inline void unregister_chrdev(unsigned int major, const char *name)
{
        __unregister_chrdev(major, 0, 256, name);
}

The register_chrdev function is used to register character devices. This function has three parameters in total, the meanings are as follows:

major : major device number, each device under linux has a device number, and the device number is divided into two parts: the major device number and the minor device number.

In linux, the major device number indicates a specific driver, and the minor device number indicates each device that uses this driver. Linux provides a data type named dev_t to indicate the device number. Dev_t is defined in include/linux/types.h, as follows :

typedef u32 __kernel_dev_t;
typedef __kernel_dev_t          dev_t;

Taken together, the dev_t type is actually an unsigned int type, which is an unsigned int type of data.

The first 10 digits of the 32 bits are the main device number, and the lower 20 bits are the minor device number. Therefore, the range of the major device number in the Linux system is 0-4095, so you must not exceed this range when selecting the major device number. In include/linux/ Several macros about device numbers are provided in kdev_t.h:

#define MINORBITS       20    // 次设备号位数
#define MINORMASK       ((1U << MINORBITS) - 1)    // 次设备号掩码

// 从设备号中获取主设备号,dev_t右移20位
#define MAJOR(dev)      ((unsigned int) ((dev) >> MINORBITS))
// 从设备号获取次设备号,取dev_t的低20位
#define MINOR(dev)      ((unsigned int) ((dev) & MINORMASK))
// 将给定的主设备号和次设备号组合成dev_t类型的设备号
#define MKDEV(ma,mi)    (((ma) << MINORBITS) | (mi))

You can use the cat /proc/devices command to view all the devices in the current system, as follows

It can be seen that the major device number 50 is not used, then we can use the major device number 50, for example:

register_chrdev(50, "chrdevbase",const struct file_operations *fops);

name : device name, pointing to a string of strings

fops : structure file_operations type pointer, pointing to the set variable of operation function of the device

The unregister_chrdev function is the same.

Several important structures:

struct inode: static file

struct file: dynamic file (opened file), there is a member variable called private_data in the file structure.

struct file_operations: describes the operation method of the device

struct cdev: The struct cdev structure is used to describe a character device, and each character device corresponds to a struct cdev structure.

Introduction to the three important structures in the driver: struct inode, struct file, struct file_operations

5. Use of man

After we have written the driver, we need to write an application to test whether the written driver is correct.

The call of the man manual is as follows (the picture comes from the Internet):

For example, the open system call, we can view the relevant header files and precautions through man 2 open

6. Driver demo of chrdevbase

Enter /dev to view the device, which is named after the module name, we can manually create the device node first:

mknod /dev/chrdevbase c 200 0 (c represents a character device, the major device number is 200, and the minor device number is 0)

chrdevbase.c

#include <linux/module.h>                                                                                                                                                                                   
#include <linux/init.h>
#include <linux/sched/signal.h>
#include <linux/device.h>
#include <linux/ioctl.h>
#include <linux/parport.h>
#include <linux/ctype.h>
#include <linux/poll.h>
#include <linux/slab.h>
#include <linux/major.h>
#include <linux/ppdev.h>
#include <linux/mutex.h>
#include <linux/uaccess.h>
#include <linux/compat.h>

#define CHRDEVBASE_MAJOR 200
#define CHRDEVBASE_NAME "chrdevbase"

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

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

// count:读取的数据量大小
static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos)
{
        printk("chrdevbase_read start\n");
        return 0;
}

static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
        printk("chrdevbase_write start\n");
        return 0;
}

static struct file_operations chrdevbase_fops = { 
        .owner = THIS_MODULE,
        .open = chrdevbase_open,
        .release = chrdevbase_release,
        .read = chrdevbase_read,
        .write = chrdevbase_write,
};

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

        printk("chrdevbase__init \n");

        // 返回值小于0就申请失败了
        ret = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops);
        if(ret < 0) {
                printk("chrdevbase__init failed!\n");
        }
        
        printk("chrdevbase__init start\n");
        return 0;
}

static void __exit chrdevbase__exit(void)
{
        unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME); 
        printk("chrdevbase__exit end\n");
}

module_init(chrdevbase__init);
module_exit(chrdevbase__exit);

Test program chrdevbaseAPP.c

#include <sys/types.h>                                                                                                                                                                                      
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>

int main(int argc, int *argv[])
{
        char *filename;
        int fd; 
        int ret;
        filename = argv[1];
        char readbuf[100], writebuf[100];

        fd = open(filename, O_RDWR);
        if (fd < 0) {
                printf("open %s failed\n", filename);
                return -1; 
        }

        ret = read(fd, readbuf, 50);
        if (ret < 0) {
                printf("read %s failed\n", filename);
                return -1; 
        } else {
    
        }
    
        ret = write(fd, writebuf, 50);
        if (ret < 0) {
                printf("write %s failed\n", filename);
                return -1; 
        } else {
    
        }

        ret = close(fd);
        if (ret < 0) {
                printf("close %s failed\n", filename);
                return -1; 
        } else {
    
        }
    

        return 0;
}

Execute the test:

./chrdevbaseAPP /dev/chrdevbase

7. Improvement of chrdevbase driver

The driver needs to use copy_to_user when transferring data to the application, and the application uses copy_from_user when transferring data to the driver. The definitions are as follows:

static inline long copy_to_user(void __user volatile *to, const void *from,
                               unsigned long n)
                               
static inline long copy_from_user(void *to, const void __user volatile *from,
                                 unsigned long n)

返回参数:成功返回0,失败返回负数                                 

chrdevbase.c

#include <linux/module.h>
#include <linux/init.h>
#include <linux/sched/signal.h>
#include <linux/device.h>
#include <linux/ioctl.h>
#include <linux/parport.h>
#include <linux/ctype.h>
#include <linux/poll.h>
#include <linux/slab.h>
#include <linux/major.h>
#include <linux/ppdev.h>
#include <linux/mutex.h>
#include <linux/uaccess.h>
#include <linux/compat.h>
#include <linux/fs.h>
#include <linux/io.h>
                                                                                                                                                                                                            
#define CHRDEVBASE_MAJOR 200
#define CHRDEVBASE_NAME "chrdevbase"

static char readbuf[100];
static char writebuf[100];
static char kerneldata[] = {"kernel data!"};

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

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

// count:读取的数据量大小
static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos)
{
        int ret = 0;

        memcpy(readbuf, kerneldata, sizeof(kerneldata));
        int ret = copy_to_user(buf, readbuf, count);
        if (ret == 0) {
                printk("copy to user success \n");
        } else {
        
        }

        return 0;
}

static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
        int ret = 0;
        ret = copy_from_user(writebuf, buf, count);
        if (ret == 0) {
                printk("copy from user success, recedata = %s \n", writebuf);
        } else {
        
        }

        return 0;
}

static struct file_operations chrdevbase_fops = {
        .owner = THIS_MODULE,
        .open = chrdevbase_open,
        .release = chrdevbase_release,
        .read = chrdevbase_read,
        .write = chrdevbase_write,
};

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

        printk("chrdevbase__init \n");

        // 返回值小于0就申请失败了
        ret = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops);
        if(ret < 0) {
                printk("chrdevbase__init failed!\n");
        }
        
        printk("chrdevbase__init start\n");
        return 0;
}

static void __exit chrdevbase__exit(void)
{
        unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME); 
        printk("chrdevbase__exit end\n");
}

module_init(chrdevbase__init);
module_exit(chrdevbase__exit);             

chrdevbaseAPP.c

#include <sys/types.h>                                                                                                                                                                                      
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

/*
 * ./chrdevbaseAPP <filename> <1/2>
 * 1 读数据
 * 2 写数据
 * */ 

int main(int argc, int *argv[])
{
        char *filename;
        int fd;
        int ret;
        filename = argv[1];
        char readbuf[100], writebuf[100];

        if (argc < 3) {
                printf("usage error \n");
        }

        fd = open(filename, O_RDWR);
        if (fd < 0) {
                printf("open %s failed\n", filename);
                return -1;
        }

        if(atoi(argv[2]) == 1) {
                ret = read(fd, readbuf, 50);
                if (ret < 0) {
                        printf("read %s failed\n", filename);
                        return -1;
                } else {
                        printf("APP read data: %s \n", readbuf);                
                }
        }

        if(atoi(argv[2]) == 2) {
                memcpy(writebuf, usrdata, sizeof(userdata));
                ret = write(fd, writebuf, 50);
                if (ret < 0) {
                        printf("write %s failed\n", filename);
                        return -1;
                } else {
                        printf("APP write data: %s \n", readbuf);
                }
        }

        ret = close(fd);
        if (ret < 0) {
                printf("close %s failed\n", filename);
                return -1;
        }

        return 0;
}        

Execute the test:

./chrdevbaseAPP /dev/chrdevbase 1

Guess you like

Origin blog.csdn.net/qq_58550520/article/details/129151803