Getting started with Linux drivers - writing your first driver

Table of contents

Preface

Driver introductory knowledge

1. How are files opened by APP represented in the kernel?

2. When opening a character device node, there is also a corresponding struct file in the kernel.

Steps to write a Hello driver

1.Process introduction

2. Driver code:

3. Application layer code:

4. Makefile content of this driver:

5. Computer experiment:


Preface

The kernel must be compiled before compiling the driver for three reasons:

  • The driver uses kernel files
  • The kernel used when compiling the driver and the kernel used when running on the development board must be consistent.
  • After replacing the kernel on the board, other drivers on the board must also be replaced.

See the article I wrote before for the steps of compiling the kernel.Compile and replace the kernel_device tree_driver_IMX6ULL-CSDN blog

Driver introductory knowledge

1. First of all, we usually open an executable file on the Linux terminal, and then the executable file will execute the program. So what does this executable file do?

2. The executable file first reads the program at the application layer, which contains many library functions. The library functions belong to the kernel. The kernel will call the driver layer program downwards. The final driver layer controls the specific hardware.

  • In fact, it is relatively easy to understand the application to the library. For example, when we first learned C language, we used functions such as printf, scanf, etc. And these functions are in the library.
  • The library can be connected to the system kernel, but I don't know exactly how to implement it.
  • When we write a driver, we need to tell the kernel. This process is called registration. After we register the driver, the driver information will be in the kernel, and then the upper-layer application can call it.

3. So we only need to know that we need to write two programs, one for the driver layer and one for the application layer. Finally, the driver layer needs to register into the kernel before the application layer can be used. Leave the rest alone.

4. We call the read function in the application layer, which corresponds to the read function in the driver layer. The write function corresponds to the write function. The open function corresponds to the open function. The close function corresponds to the release function (I don’t know why this is different).

5. After we have a brief understanding of the driver calling process by Linux applications, I need to know how the entire program writing process should be done. As for why the process is like this, we can just remember it. Because these are all prescribed by people, it will not be too late to study them in depth later if you learn more deeply. Now we are mainly getting started.     

1. How are files opened by APP represented in the kernel?

When APP opens a file, it can get an integer, which is called a file handle. For each file handle of the APP, there is a corresponding "struct file" in the kernel.

When we use open to open a file, the passed flags, mode and other parameters will be recorded in the corresponding struct file structure in the kernel (f_flags, f_mode):

int open(const char *pathname, int flags, mode_t mode);

When reading and writing a file, the current offset address of the file will also be stored in the f_pos member of the struct file structure.

2. When opening a character device node, there is also a corresponding struct file in the kernel.

Note the structure in this structure: struct file_operations *f_op, which is provided by the driver.

Structure struct file_operations is defined as follows:

Steps to write a Hello driver

The main steps are as follows:

  1. Determine the major device number, or let the kernel assign it
  2. Define your own file_operations structure
  3. Implement the corresponding drv_open/drv read/drv write and other functions and fill in the file operations structure
  4. Tell the kernel the file_operations structure: register_chrdev
  5. Who will register the driver? There must be an entry function: when installing the driver, this entry function will be called
  6. If there is an entry function, there should be an exit function: When uninstalling the driver, the exit function calls unregister_chrdev
  7. Other improvements: Provide device information and automatically create device nodes: class_create, device_create

1.Process introduction

<1>We first need to write a file_operations type structure, which is used to manage the driver. After we register the driver into the kernel, we call the driver at the application layer, and then we can directly operate the open, write, read and other functions in the driver through this structure.

<2>Implement the corresponding drv_open/drv_read/drv_write and other functions and fill in the file_operations structure. In this way, when we call open, write, read and other functions in the application layer, we are calling this driver.

At this time, someone may ask, there are so many drivers, how do I know which driver corresponds to open? It's very simple. When we write an application layer program, do we need to pass in a device number as the first parameter? The system uses this device number to determine which driver is called.

<3>Tell the kernel the file_operations structure: register_chrdev. We wrote a driver, but the kernel doesn't know about it. So what to do? We just register it, the kernel understands that it has this driver, and then assigns it a device number. Then the application layer can call the driver layer based on this device number.

<4> At this time, some people have questions, who will register this structure? So we need an entry function to register. When installing the driver, this entry function will be called.

<5>If there is an entry function, there should be an exit function: when uninstalling the driver, the exit function calls unregister_chrdev.

<6>Finally, you need to join the GPL agreement. Because Linux complies with the GPL agreement, if you need to use other driver layer functions of Linux, you must comply with the GPL agreement and open source code is mandatory. According to this agreement, you can ask all manufacturers using Linux to provide driver layer source code, and others can also ask you to disclose your driver layer code. This is mutual. However, in order to circumvent this protocol, many manufacturers keep the driver source code very simple and put complex things in the application layer. As for adding the author's name, feel free to write it or not.                           

2. Driver code:

hello_drv.c

#include <linux/module.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>

/* 1. 确定主设备号                                                                 */
static int major = 0;
static char kernel_buf[1024];
static struct class *hello_class;


#define MIN(a, b) (a < b ? a : b)

/* 3. 实现对应的open/read/write等函数,填入file_operations结构体                   */
static ssize_t hello_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	int err;
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	err = copy_to_user(buf, kernel_buf, MIN(1024, size));
	return MIN(1024, size);
}

static ssize_t hello_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
	int err;
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	err = copy_from_user(kernel_buf, buf, MIN(1024, size));
	return MIN(1024, size);
}

static int hello_drv_open (struct inode *node, struct file *file)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}

static int hello_drv_close (struct inode *node, struct file *file)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}

/* 2. 定义自己的file_operations结构体                                              */
static struct file_operations hello_drv = {
	.owner	 = THIS_MODULE,
	.open    = hello_drv_open,
	.read    = hello_drv_read,
	.write   = hello_drv_write,
	.release = hello_drv_close,
};

/* 4. 把file_operations结构体告诉内核:注册驱动程序                                */
/* 5. 谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数 */
static int __init hello_init(void)
{
	int err;
	
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	major = register_chrdev(0, "hello", &hello_drv);  /* /dev/hello */


	hello_class = class_create(THIS_MODULE, "hello_class");
	err = PTR_ERR(hello_class);
	if (IS_ERR(hello_class)) {
		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		unregister_chrdev(major, "hello");
		return -1;
	}
	
	device_create(hello_class, NULL, MKDEV(major, 0), NULL, "hello"); /* /dev/hello */
	
	return 0;
}

/* 6. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数           */
static void __exit hello_exit(void)
{
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	device_destroy(hello_class, MKDEV(major, 0));
	class_destroy(hello_class);
	unregister_chrdev(major, "hello");
}


/* 7. 其他完善:提供设备信息,自动创建设备节点                                     */

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");


3. Application layer code:

hello_drv_test.c


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

/*
 * ./hello_drv_test -w abc
 * ./hello_drv_test -r
 */
int main(int argc, char **argv)
{
	int fd;
	char buf[1024];
	int len;
	
	/* 1. 判断参数 */
	if (argc < 2) 
	{
		printf("Usage: %s -w <string>\n", argv[0]);
		printf("       %s -r\n", argv[0]);
		return -1;
	}

	/* 2. 打开文件 */
	fd = open("/dev/hello", O_RDWR);
	if (fd == -1)
	{
		printf("can not open file /dev/hello\n");
		return -1;
	}

	/* 3. 写文件或读文件 */
	if ((0 == strcmp(argv[1], "-w")) && (argc == 3))
	{
		len = strlen(argv[2]) + 1;
		len = len < 1024 ? len : 1024;
		write(fd, argv[2], len);
	}
	else
	{
		len = read(fd, buf, 1024);		
		buf[1023] = '\0';
		printf("APP read : %s\n", buf);
	}
	
	close(fd);
	
	return 0;
}


How to compile .c file into driver .ko?

This requires the help of the top-level Makefile of the kernel. First, set up the cross-compilation tool chain, compile the kernel used by your board, then modify the Makefile to specify the kernel source code path, and finally execute the make command to compile the driver and test program.

4. Makefile content of this driver:

KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88

all:
        make -C $(KERN_DIR) M=`pwd` modules
        $(CROSS_COMPILE)gcc -o hello_drv_test hello_drv_test.c

clean:
        make -C $(KERN_DIR) M=`pwd` modules clean
        rm -rf modules.order
        rm -f hello_drv_test

obj-m   += hello_drv.o

5. Computer experiment:

Execute the make command to compile the driver and test program

After starting the board, you can mount a directory in Ubuntu through NFS and access the programs in the directory.

Turn on kernel printing: echo "7 4 1 7" > /proc/sys/kernel/printk

Turn off kernel printing: echo 0 4 0 7 > /proc/sys/kernel/printk

insmod is the abbreviation of install module (loading module)

insmod hello_drv.ko loads the driver

ls /dev/hello -l // The driver will generate device nodes. The driver will generate device nodes.

lsmod confirms that the driver has been installed

We know that the driver has been installed, then we need to know the device number of the driver

cat /proc/devices, view the device numbers currently in use

The driver name is related to the second parameter of the register_chrdev() function we use at the driver layer.

./hello_drv_test // View the usage of the test program

./hello_drv_test -w zglnb // Write a string to the driver

./hello_drv_test -r // Read the string from the driver

Guess you like

Origin blog.csdn.net/m0_74712453/article/details/134927733