i.MX6ULL learning notes -- driver function initialization

brief introduction

From the perspective of Linux architecture, the kernel system has regarded the driver as an independent sub module. Therefore, the driver module can load (insmod) and unload (remmod) when the kernel system is running.

From a functional point of view, the driver module undertakes the function of mapping system function calls and hardware actions. That is, it provides the interface for the kernel system to operate the hardware. In the process, the user calls the system function to enter the kernel space, and the kernel uses the super permission to call the driver interface function, so as to realize the control of the hardware! In this way, mechanisms (how to drive hardware) and policies (how to use hardware) can be operated separately.

Each module consists of object code, but it is not actually compiled into an executable program!

Linux system divides device modules into three categories: character module (a device that can be accessed like a byte stream like a file), block module or network module.

No matter which module it is, you must remind yourself that the driver can be re entered when developing the driver, that is, the driver may be called by multiple programs through the kernel system at the same time!

Compile kernel

It is worth noting that because the driver module runs in the kernel space, the driver can only call the function library provided by the kernel. For example, the printk function (which is different from the printf function).

//Driver source code, a driver without practical function
#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("DUAL BSD/GPL");//If it is not declared to the kernel, the kernel will issue a warning at compile time

static int hello_init(void)
{
	printk(KERN_ALERT "hello,world");
	return 0;
}

static int exit_init(void)
{
	printk(KERN_ALERT "Goodbye");
}

module_init(hello_init);//After the driver is loaded by the super user, it will enter the specified entry
module_exit(hello_exit);//After the driver is unloaded by the super user, it will enter the specified entry

MODULE_LICENSE("GPL2");
MODULE_AUTHOR("xxx");
MODULE_ALIAS("xxx");

The compilation process is the same as that of ordinary programs, which needs the help of make tool

#makefile file in drive directory
KERNEL_DIR=/home/x/Linux_info/ebf-buster-linux/build_image/build

ARCH=arm
CROSS_COMPILE=arm-linux-gnueabihf-
export ARCH CROSS_COMPILE #Pass parameters ARCH and CROSS_COMPILE to next level makefile

obj-m := helloworld.o
all:
	$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) modules#In the makefile, $(CURDIR) defaults to the current directory

.PHONE:clean copy

clean:
	$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) clean
copy:
	sudo cp *.ko /home/x/info_baidu/workspace

This makefile describes that the kernel needs to generate a hello Ko file, and this file needs to be from hello O. In order to call the kernel driver to compile the makefile file, you need to use the make -C instruction to the specified directory (the build directory generated during kernel source code compilation) and then run the make instruction.

In fact, the ~ / kernel-2.6 directory is not fixed. It relies on the configuration file when the kernel source code is compiled. In the ubuntu system, the source code is not suitable for direct compilation, and tools and source code need to be downloaded

Use the command apt install make GCC arm linux gnueabihf GCC bison flex libssl dev dpkg dev lzop to download the tool

Through the instruction git clone https://gitee.com/Embedfire/ebf-buster-linux.git Download the source code to the current directory

In the source directory, there is a file named make_deb.sh script file, which has a code build_opts="${build_opts}O=build_image/build" indicates the path of the top-level makefile file of the kernel.

Run the make command directly under the source code path and wait a little to complete the compilation!

#Top level makefile file
# Automatically generated by /home/x/Linux_info/ebf-buster-linux/scripts/mkmakefile: don't edit

VERSION = 4
PATCHLEVEL = 19

lastword = $(word $(words $(1)),$(1)) #Reads the last string of the first parameter
makedir := $(dir $(call lastword,$(MAKEFILE_LIST))) #Execution function

ifeq ("$(origin V)", "command line")
VERBOSE := $(V)
endif
ifneq ($(VERBOSE),1)
Q := @
endif

MAKEARGS := -C /home/x/Linux_info/ebf-buster-linux
MAKEARGS += O=$(if $(patsubst /%,,$(makedir)),$(CURDIR)/)$(patsubst %/,%,$(makedir))

MAKEFLAGS += --no-print-directory

.PHONY: __sub-make $(MAKECMDGOALS) 



__sub-make:
	$(Q)$(MAKE) $(MAKEARGS) $(MAKECMDGOALS) #Enter the directory of the source code and execute make

$(filter-out __sub-make, $(MAKECMDGOALS)): __sub-make
	@:

It is not difficult to verify the appeal variables. You can output the variables you want to know through the echo command!

initialization

Two header files are required for initialization. Please refer to the appendix for details.

//Initialization function
#include <linux/init.h>
#include <linux/module.h>
static int __init initialization_function(void)
{
	printk(KERN_ALERT "hello");
}

module_init(initialization_function);//After the driver is loaded by the super user, it will enter the specified entry
module_exit(xxx);//After the driver is unloaded by the super user, it will enter the specified entry

Initialization function__ The init flag is a reminder to the kernel that this code will not exist in the memory space for a long time. It can be removed from the memory after the module initialization is completed and the function is called.

When writing driver initialization, you should always be alert to initialization failure. Drivers that fail to initialize in the kernel cannot be unloaded, so you need to judge and handle them in the initialization function.

There are two options:
1. When a goto statement is used to determine an error, directly jump to the corresponding address and uninstall the part that has been successfully installed.
2. Each successfully registered driver is recorded. If an error is judged, all successfully registered drivers are unloaded in the form of rollback.

Loading and unloading

After the driver is compiled, *. Is generated Ko file and copy the file to the environment to be mounted. And enter the instruction insmod. / * ko
After loading, the terminal will execute the tasks in the initialization function.
After the terminal executes the insmod instruction, call the macro SYSCALL_DEFINE3(); Add * ko file is transferred to the program space, and then a series of operations (transformation of ELF file format) are carried out. Finally, it is loaded successfully.

//From the source code kernel / module s
#include <linux/syscalls.h>

SYSCALL_DEFINE3(init_module, void __user *, umod,
		unsigned long, len, const char __user *, uargs)
{
	int err;
	struct load_info info = { };

	err = may_init_module();
	if (err)
		return err;

	pr_debug("init_module: umod=%p, len=%lu, uargs=%p\n",
	       umod, len, uargs);

	err = copy_module_from_user(umod, len, &info);		//Copy
	if (err)
		return err;

	return load_module(&info, uargs, 0);			//load
}

SYSCALL_DEFINE3(finit_module, int, fd, const char __user *, uargs, int, flags)
{
	struct load_info info = { };
	loff_t size;
	void *hdr;
	int err;

	err = may_init_module();
	if (err)
		return err;

	pr_debug("finit_module: fd=%d, uargs=%p, flags=%i\n", fd, uargs, flags);

	if (flags & ~(MODULE_INIT_IGNORE_MODVERSIONS
		      |MODULE_INIT_IGNORE_VERMAGIC))
		return -EINVAL;

	err = kernel_read_file_from_fd(fd, &hdr, &size, INT_MAX,
				       READING_MODULE);
	if (err)
		return err;
	info.hdr = hdr;
	info.len = size;

	return load_module(&info, uargs, flags);
}

The kernel has gradually updated the expression of system functions, and the functions are accessed through a unified channel through macro encapsulation. For example, the module C, use the library < Linux / syscalls h>

//form <linux/syscalls.h>
#define SYSCALL_METADATA(sname, nb, ...)

#define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__)


#define SYSCALL_DEFINEx(x, sname, ...)				\
	SYSCALL_METADATA(sname, x, __VA_ARGS__)			\
	__SYSCALL_DEFINEx(x, sname, __VA_ARGS__)

/*
 * The asmlinkage stub is aliased to a function named __se_sys_*() which
 * sign-extends 32-bit ints to longs whenever needed. The actual work is
 * done within __do_sys_*().
 */
#ifndef __SYSCALL_DEFINEx
#define __SYSCALL_DEFINEx(x, name, ...)					\
	__diag_push();							\
	__diag_ignore(GCC, 8, "-Wattribute-alias",			\
		      "Type aliasing is used to sanitize syscall arguments");\
	asmlinkage long sys##name(__MAP(x,__SC_DECL,__VA_ARGS__))	\
		__attribute__((alias(__stringify(__se_sys##name))));	\
	ALLOW_ERROR_INJECTION(sys##name, ERRNO);			\
	static inline long __do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__));\
	asmlinkage long __se_sys##name(__MAP(x,__SC_LONG,__VA_ARGS__));	\
	asmlinkage long __se_sys##name(__MAP(x,__SC_LONG,__VA_ARGS__))	\
	{								\
		long ret = __do_sys##name(__MAP(x,__SC_CAST,__VA_ARGS__));\
		__MAP(x,__SC_TEST,__VA_ARGS__);				\
		__PROTECT(x, ret,__MAP(x,__SC_ARGS,__VA_ARGS__));	\
		return ret;						\
	}								\
	__diag_pop();							\
	static inline long __do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__))
#endif /* __SYSCALL_DEFINEx */

Module parameters

Module parameters are somewhat similar to global variables. Variables are usually used in functions to pass some states, while parameters are used between modules. In order to pass parameters, the kernel provides related macro definitions

//parament.c
//Parameter transfer
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>

static int para_i = 0;
module_param(para_i,int,0);

static bool para_b = 0;
module_param(para_b,bool,0);

static char para_c = 0;
module_param(para_c,byte,0);

static char * para_cp = 0;
module_param(para_cp ,charp,0);



static int __init param_init(void)
{
	printk(KERN_ALERT "the module of param is initial!\n");
	printk(KERN_ALERT "para_i = %d \n",para_i);
	printk(KERN_ALERT "para_b = %d \n",para_b);
	printk(KERN_ALERT "para_c = %d \n",para_c);
	printk(KERN_ALERT "para_cp = %s \n",para_cp);
	return 0;
}

static void __exit param_exit(void)
{
	printk(KERN_ALERT "the module of param is rmmod!\n");
}

static int my_add(int,a,int b)
{
	return a+b;
}

EXPORT_SYMBOL(para_i);//Shared para_i
EXPORT_SYMBOL(my_add);//Shared function

static int my_sub(int,a,int b)
{
	return a-b;
}

EXPORT_SYMBOL(my_sub);



module_init(param_init);//After the driver is loaded by the super user, it will enter the specified entry
module_exit(param_exit);//After the driver is unloaded by the super user, it will enter the specified entry

//calculation.c
//Parameter transfer
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>

#include <calculation.h>


static int __init calculation_init(void)
{
	printk(KERN_ALERT "the module of calculation is initial!\n");
	printk(KERN_ALERT "para_i - 1  = %d ,para_i + 1 = %d  \n",my_add(para_i,i),my_sub(para_i,1));
	return 0;
}

static void __exit calculation_exit(void)
{
	printk(KERN_ALERT "the module of param is rmmod!\n");
}



module_init(calculation_init);//After the driver is loaded by the super user, it will enter the specified entry
module_exit(calculation_exit);//After the driver is unloaded by the super user, it will enter the specified entry

Declare the parameter para in the first module_ I and function my_add(); After the param module is declared, the calculation module will be declared normally, otherwise an error will occur because the calculation module references some external identifiers.

The unloading process is the opposite, and the principle is the same as taking off boots.

It is worth noting that the parameter para is declared_ The type used in C is byte.

appendix

Header file

	#include <linux/init. h> : the purpose of this header file is to specify initialization and explicit functions
	#include <linux/module. h> : this header file contains a large number of symbols and function definitions required by loadable modules

Related macro

module_init();
module_exit();
module_param();

correlation function

Keywords: Linux Embedded system

Added by ghjr on Mon, 17 Jan 2022 13:15:50 +0200