Arm linux driver introduction and hands-on teaching you simple driver writing

I'm the Ming Hai bu 2022-05-22 12:45:08 阅读数:94

armlinuxdriverintroductionhands-on

0. What this article will explain

  1. Write the materials needed for driving by yourself
  2. A brief introduction to compiler driven Makefile file
  3. Write a simple driver , And tell how to check whether it is loaded correctly
  4. Introduce several callback pointers commonly used by driver
  5. Write a driver , Use ioctl, Interaction between application state and kernel state

1. Write the materials needed for driving by yourself

  • Cross tool chain corresponding to the platform , The tool chain in this article is (arm-at91-linux-gnueabi-gcc)
  • The compileable kernel of the corresponding platform , That is, it has been carried out menuconfig Kernel source code of .( The main difference between online and offline kernel source code packages is menuconfig After that, a .config file )

2. A brief introduction to compiler driven Makefile file

Driven Makefile It's not like the application layer makefile So strange , It can be said to be driven Makefile Is a fixed template , Just copy it and use it .

Driven Makefile

# Makefile
ifneq ($(KERNELRELEASE),)
obj-m := xyy.o
else
#KERNELDIR ?= /lib/modules/$(shell uname -r)/build
KERNELDIR ?= /path_to_linux_src/linux-at91-linux-2.6.39-at91-20160713
PWD := $(shell pwd)
default: clean
make -C $(KERNELDIR) M=$(PWD) modules ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi-
rm -rf *.o .*.cmd .tmp_versions *.mod.c modules.order Module.symvers *.ko.unsigned
clean:
$(RM) -r *.ko *.o .*.cmd .tmp_versions *.mod.c modules.order Module.symvers *.ko.unsigned
endif

Let's briefly talk about this Makefile How to use

The total number of changes needed here is Three places

  1. Namely obj-m hinder .o File name of , Change to your own driven .c Name of file ;
  2. KERNELDIR Change to linux The folder address of the kernel source code , remember , If menuconfig Yes .
  3. CROSS_COMPILE Change this variable to the prefix of your board cross compilation tool chain .

After these three changes , You can go directly to makefile Enter the current directory of make Yes .

Here we need to pay attention to , Why obj-m:

  • obj-y +=xxx.o The module is compiled to zImage

  • obj-m +=xxx.o This module will not compile to zImage, But it will generate a separate xxx.ko Static compilation

If you use obj-y be make Nothing will happen after that .

Simply tell me why it is obj-m This thing

We can simply look at the of a component in a kernel source code Makefile file , Here we use /arch/arm/mach-at91/Makefile As an example :

# CPU-specific support
obj-$(CONFIG_ARCH_AT91RM9200) += at91rm9200.o at91rm9200_time.o at91rm9200_devices.o
obj-$(CONFIG_ARCH_AT91SAM9260) += at91sam9260.o at91sam926x_time.o at91sam9260_devices.o sam9_smc.o at91sam9_alt_reset.o
obj-$(CONFIG_ARCH_AT91SAM9261) += at91sam9261.o at91sam926x_time.o at91sam9261_devices.o sam9_smc.o at91sam9_alt_reset.o
obj-$(CONFIG_ARCH_AT91SAM9G10) += at91sam9261.o at91sam926x_time.o at91sam9261_devices.o sam9_smc.o at91sam9_alt_reset.o
obj-$(CONFIG_ARCH_AT91SAM9263) += at91sam9263.o at91sam926x_time.o at91sam9263_devices.o sam9_smc.o at91sam9_alt_reset.o
obj-$(CONFIG_ARCH_AT91SAM9RL) += at91sam9rl.o at91sam926x_time.o at91sam9rl_devices.o sam9_smc.o at91sam9_alt_reset.o
obj-$(CONFIG_ARCH_AT91SAM9G20) += at91sam9260.o at91sam926x_time.o at91sam9260_devices.o sam9_smc.o at91sam9_alt_reset.o
obj-$(CONFIG_ARCH_AT91SAM9G45) += at91sam9g45.o at91sam926x_time.o at91sam9g45_devices.o sam9_smc.o
obj-$(CONFIG_ARCH_AT91SAM9X5) += at91sam9x5.o at91sam926x_time.o at91sam9x5_devices.o sam9_smc.o
obj-$(CONFIG_ARCH_AT91SAM9N12) += at91sam9n12.o at91sam926x_time.o at91sam9n12_devices.o sam9_smc.o
obj-$(CONFIG_ARCH_AT91CAP9) += at91cap9.o at91sam926x_time.o at91cap9_devices.o sam9_smc.o
obj-$(CONFIG_ARCH_AT572D940HF) += at572d940hf.o at91sam926x_time.o at572d940hf_devices.o sam9_smc.o
obj-$(CONFIG_ARCH_AT91X40) += at91x40.o at91x40_time.o

You can see a lot of obj-${xxxx} Of Makefile Variable , And these variables use macros , In fact, it is menuconfig When you make a selection in, you copy , We can do it in .config Find clues in the document .

# Automatically generated make config: don't edit
# Linux/arm 2.6.39 Kernel Configuration
# Wed Mar 27 14:37:50 2019
#
CONFIG_ARM=y
CONFIG_SYS_SUPPORTS_APM_EMULATION=y
CONFIG_GENERIC_GPIO=y
# CONFIG_ARCH_USES_GETTIMEOFFSET is not set
CONFIG_GENERIC_CLOCKEVENTS=y
CONFIG_KTIME_SCALAR=y
CONFIG_HAVE_PROC_CPU=y
CONFIG_STACKTRACE_SUPPORT=y
CONFIG_HAVE_LATENCYTOP_SUPPORT=y
CONFIG_LOCKDEP_SUPPORT=y
CONFIG_TRACE_IRQFLAGS_SUPPORT=y
CONFIG_HARDIRQS_SW_RESEND=y
CONFIG_GENERIC_IRQ_PROBE=y

Actually menuconfig The process is to give Makefile The values in , Classify different target files in a regular way . Be commonly called Kernel clipping , This part will not be repeated here .

3. Write a simple driver , And tell how to check whether it is loaded correctly

/* * @Author: Adam Xiao * @Date: 2021-03-23 19:40:28 * @LastEditors: Adam Xiao * @LastEditTime: 2021-03-25 10:39:18 * @FilePath: /test/xyy.c */
// xyy.c
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/slab.h>
#include <asm/gpio.h>
#include <asm/uaccess.h> 
#include <linux/delay.h>
#include <linux/spinlock.h>
#include <linux/miscdevice.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/version.h>
#include <linux/timer.h>
#include <linux/timex.h>
#include <linux/rtc.h>
#include <linux/workqueue.h>
#include <asm/atomic.h>
struct miscdevice misc_dev =
{

.minor = MISC_DYNAMIC_MINOR,
.name = "Adam",
};
static int __init test_init(void)
{

int ret = misc_register(&misc_dev);
if (ret) {

printk(KERN_ERR "[xyy]misc_register error\n");
} else {

printk(KERN_INFO "[xyy]test driver init\n");
}
return ret;
}
static void __exit test_exit(void)
{

misc_deregister(&misc_dev);
printk(KERN_INFO "[xyy]test driver exit\n");
}
module_init(test_init);
module_exit(test_exit);
MODULE_AUTHOR("XXXXXBBBBB Corporation");
MODULE_DESCRIPTION("XXXXXXXX machine main controller board IO test driver");
MODULE_LICENSE("Dual BSD/GPL");
MODULE_VERSION("1.0.0");

The test source code is as described above , Go straight ahead make.

[email protected]$make
rm -f -r *.ko *.o .*.cmd .tmp_versions *.mod.c modules.order Module.symvers *.ko.unsigned
make -C /XXXXXXXXXXXXXXXXXXXXX/linux-at91-linux-2.6.39-at91-20160713 M=/XXXXXXXXXXXXXXXXXXXXX/test modules ARCH=arm CROSS_COMPILE=arm-at91-linux-gnueabi-
make[1]: Entering directory '/XXXXXXXXXXXXXXXXXXXXX/linux-at91-linux-2.6.39-at91-20160713'
CC [M] /XXXXXXXXXXXXXXXXXXXXX/test/xyy.o
Building modules, stage 2.
MODPOST 1 modules
CC /XXXXXXXXXXXXXXXXXXXXX/test/xyy.mod.o
LD [M] /XXXXXXXXXXXXXXXXXXXXX/test/xyy.ko
make[1]: Leaving directory '/XXXXXXXXXXXXXXXXXXXXX/linux-at91-linux-2.6.39-at91-20160713'
rm -rf *.o .*.cmd .tmp_versions *.mod.c modules.order Module.symvers *.ko.unsigned

The first exciting drive is fresh out . Here are three simple instructions

  • insmod: Load the corresponding driver
  • rmmod: Unload the corresponding driver
  • dmesg: View kernel print ,-c Is to clear the history print cache , But the last cache will be displayed , It will not be cleared until it is displayed .

Let's try , Just compiled driver xyy.ko.

# insmod xyy.ko
# dmesg -c
[xyy]test driver init
# rmmod xyy.ko
# dmesg -c
[xyy]test driver exit
# ls /dev/Adam -laht
crw-rw---- 1 root root 10, 57 Mar 25 14:25 /dev/Adam

You can see , When this driver is loaded into the system , The kernel prints what we encode . When unloading the driver , Also printed the relevant contents of unloading the driver . And in /dev Under the table of contents , One more named Adam The device file for .

Please note that 10,57 This number , The meaning of these two numbers will be explained in detail later .

4. Introduce the interface of driver , And several commonly used callback pointers

The above driver can simply run , It shows that our framework is correct . Let's explain it briefly , Some special interfaces in the kernel .

4.1 MODULE_AUTHOR A series of macro

As in the last part of the code, there are some such macros , In fact, it means that your driver belongs to .

MODULE_AUTHOR("XXXXXBBBBB Corporation");
MODULE_DESCRIPTION("XXXXXXXX machine main controller board IO test driver");
MODULE_LICENSE("Dual BSD/GPL");
MODULE_VERSION("1.0.0");

It's not mandatory , But there is a saying :

Not strictly required , But your module should really specify which license its code uses . To do this, just include one line MODULE_LICENSE: MODULE_LICENSE(“GPL”); The specific licenses recognized by the kernel are , “GPL”( apply GNU Any version of the general public license ), “GPL v2”( Applicable only GPL edition 2 ), “GPL and additional rights”, “Dual BSD/GPL”, “Dual MPL/GPL”, and “Proprietary”. Unless your module is clearly identified under a free license recognized by the kernel , Otherwise, assume that it is private , The kernel is when the module is loaded " Make dirty " 了 . Like we were in the first 1 Chapter " License terms " Mentioned in , Kernel developers are not enthusiastic about helping users who encounter problems after loading private modules .

In short, we still have the spirit of open source .

4.2 module_init and module_exit

These two essences are also macro . use C++ Understand the concept of , It can be understood as constructor and destructor , The function that runs when the driver loads , use module_init This interface is registered , The function that runs when the driver is unloaded , use module_exit This interface is registered . Then when the driver performs the corresponding operation , The corresponding function will be run .

These two functions , Equivalent to the entry and exit functions of the driver .

4.3 misc_register and misc_deregister

Here are two concepts , Miscellaneous equipment (misc_device) And character devices (char_device).

Several types of devices

  • Miscellaneous equipment misc_device

Miscellaneous devices are also widely used in embedded systems as device drivers . stay Linux Kernel include/linux Directory is Miscdevice.h file , I want to define myself miscdevice The slave device is defined here . In fact, it is because these character devices do not conform to the predetermined category of character devices , All of these devices use the editor in chief number 10, Together to miscdevice, Actually misc_register It's the main label 10 call register_chrdev() Of .

in other words ,misc A device is a special character device , Equipment nodes can be generated automatically .

  • Character device (char_device)

Use register_chrdev(LED_MAJOR,DEVICE_NAME,&dev_fops) When registering a character device driver , If multiple devices use this function to register drivers ,LED_MAJOR Cannot be the same , Otherwise, several devices will not be able to register . If the module is registered in this way and LED_MAJOR by 0( Automatically assign master device number ), Use insmod When the command loads the module, the assigned master device number and secondary device number will be displayed on the terminal , stay /dev Create the node under the directory , Like equipment leds, If the master device number and secondary device number assigned when loading the module are 253 and 0, The following statement is used when establishing a node :
mknod leds c 253 0

Use register_chrdev(LED_MAJOR,DEVICE_NAME,&dev_fops) When registering the character device driver, the node should be established manually , Otherwise, the device cannot be opened in the application .

The main difference is

It is convenient to register and load miscellaneous equipment , There is no need to manually specify the primary and secondary equipment numbers :

insmod xyy.ko

This completes the loading

When the character device is registered , You need to manually specify the equipment number , And when loading , Need to call mknod To establish the node :

insmod Adam.ko
mknod /dev/Adam c 234 0

mknod The standard form of is : mknod DEVNAME {b | c} MAJOR MINOR

  1. DEVNAME Is the name of the device file to create , If you want to put device files in a specific folder , You need to use mkdir stay dev Create a new directory under the directory ;
  2. b and c Block device and character device respectively :
    b Indicates when the system reads data from a block device , Directly from memory buffer Read data from , Without going through the disk ;
    c Indicates that when the character device files and devices transmit data, they are transmitted in the form of characters , Send one character at a time , Like a printer 、 Terminals transmit data in the form of characters ;
  3. MAJOR and MINOR It represents the main equipment number and the secondary equipment number respectively :
    In order to manage the equipment , The system assigns a number... To each device , A device number consists of a primary device number and a secondary device number . The main equipment number indicates a certain kind of equipment , The secondary device number is used to distinguish the same type of device .linux The device file number is assigned in the operating system 32 Bit unsigned integer , The top 12 Bit is the main equipment number , after 20 Bit is the secondary equipment number , Therefore, when applying for equipment documents from the system, the main equipment number should not exceed 4095, The number of secondary equipment should not exceed 2^20 -1.

What needs to be noted here is ,mknod When the primary and secondary device numbers need to be registered with the driver code , bring into correspondence with .

5. Write a driver , Use ioctl, Interaction between application state and kernel state

Although the above driver is correct , But in fact, this driver does nothing .

Back to the essence of driving , What is driving for ?

The driver is used to control the hardware

Because the user layer has no authority to directly control the hardware , Only the kernel can do this , So we need a bridge , To notify the kernel , What hardware do I want to control , Use this hardware to read or write something .

Generally speaking , Reading the device or reading the value of the chip , The manufacturer will give interface samples , for example :

at91_set_gpio_value(CPLD_ADDR_4,1);
at91_set_gpio_value(CPLD_ADDR_1,1);

In fact, an interface like this can literally guess what it does , The above two interfaces are for gpio A pin of is set with 1 This value , Anyway, it's either high or low .

This interface can only be used in kernel mode , So you need to write a driver , Complete certain functions , Then get these / Set the value to the application state program . Every driver does this .

5.1 First on the source code

This debug.h It is mainly used to assist printing , Unify the printing of application layer and kernel layer .

//debug.h
#ifndef __DEBUG_H__
#define __DEBUG_H__
#define COL_DEF "\033[m"
#define COL_RED "\033[0;32;31m"
#define COL_GRE "\033[0;32;32m"
#define COL_BLU "\033[0;32;34m"
#define COL_YEL "\033[1;33m"
#ifdef __KERNEL__ //for linux driver
#define ERR(fmt, ...) printk(KERN_ERR COL_RED "driver error function[%s]:"\ COL_YEL fmt COL_DEF "\n", __func__, ##__VA_ARGS__)
#define INFO(fmt, ...) printk(KERN_INFO COL_GRE "driver information:"\ COL_YEL fmt COL_DEF "\n", ##__VA_ARGS__)
#ifdef DEBUG
#define DBG(fmt, ...) printk(KERN_DEBUG COL_BLU "debug function[%s]:"\ COL_DEF fmt, __func__, ##__VA_ARGS__)
#else
#define DBG(fmt, ...) ({0;})
#endif
#else //for linux application
#define ERR(fmt, ...) printf(COL_RED "error function[%s]:"\ COL_YEL fmt COL_DEF "\n", __func__, ##__VA_ARGS__)
#define INFO(fmt, ...) printf(COL_GRE "information:"\ COL_YEL fmt COL_DEF "\n", ##__VA_ARGS__)
#ifdef DEBUG
#define DBG(fmt, ...) printf(COL_BLU "debug function[%s]:"\ COL_DEF fmt, __func__, ##__VA_ARGS__)
#else
#define DBG(fmt, ...) ({0;})
#endif
#endif
#endif

Here is the core code of the driver .

/* * @Author: Adam Xiao * @Date: 2021-03-23 19:40:28 * @LastEditors: Adam Xiao * @LastEditTime: 2021-03-25 17:04:13 * @FilePath: /test/xyy.c */
// xyy.c
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/slab.h>
#include <asm/gpio.h>
#include <asm/uaccess.h> 
#include <linux/delay.h>
#include <linux/spinlock.h>
#include <linux/miscdevice.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/version.h>
#include <linux/timer.h>
#include <linux/timex.h>
#include <linux/rtc.h>
#include <linux/workqueue.h>
#include <asm/atomic.h>
#include "debug.h"
static int io_open(struct inode *inode, struct file *filp)
{

INFO("[xyy] Open called!!!");
return 0;
}
static int io_release(struct inode *inode, struct file *file)
{

INFO("[xyy] close called!!!");
return 0;
}
struct test {

int a;
short b;
char c;
};
long io_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{

INFO("cmd = %#x\n", cmd);
INFO("arg:%p\n", (void *)arg);
struct test t = {
0, 0, 0};
int ret = copy_from_user(&t, (struct test *)arg, sizeof(t));
INFO("ret = %d, t.a = %d, t.b = %d, t.c = %d\n", ret, t.a, t.b, t.c);
t.a = 111;
t.b = 222;
t.c = 33;
(void) copy_to_user((struct test *)arg, &t, sizeof(t));
return 0;
}
struct file_operations io_ops =
{

.owner = THIS_MODULE,
.release = io_release,
.open = io_open,
#if 0
.read = irq_read,
#endif
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,18)
.unlocked_ioctl = io_ioctl,
#else
.ioctl = io_ioctl,
#endif
};
struct miscdevice misc_dev =
{

.minor = MISC_DYNAMIC_MINOR,
.name = "Adam",
.fops = &io_ops,
};
static int __init test_init(void)
{

int ret = misc_register(&misc_dev);
if (ret) {

printk(KERN_ERR "[xyy]misc_register error\n");
} else {

printk(KERN_INFO "[xyy]test driver init\n");
}
return ret;
}
static void __exit test_exit(void)
{

misc_deregister(&misc_dev);
printk(KERN_INFO "[xyy]test driver exit\n");
}
module_init(test_init);
module_exit(test_exit);
MODULE_AUTHOR("XXXXXBBBBB Corporation");
MODULE_DESCRIPTION("XXXXXXXX machine main controller board IO test driver");
MODULE_LICENSE("Dual BSD/GPL");
MODULE_VERSION("1.0.0");

You can see , Compared to the previous code , We mainly registered one more in miscellaneous equipment file_operations This structure , What does this structure do ? We know ,linux The spirit of is that everything is a document , The same is true for driven devices , So it is also manipulated as a file .

struct file_operations io_ops =
{

.owner = THIS_MODULE,
.release = io_release,
.open = io_open,
#if 0
.read = irq_read,
#endif
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,18)
.unlocked_ioctl = io_ioctl,
#else
.ioctl = io_ioctl,
#endif
};

You can guess boldly by looking at the definition of members here , It's nothing more than registering functions corresponding to many actions , for example open When this device , Will execute io_open This function ,ioctl When , Will execute io_ioctl This function ,close When , Will execute io_release This function .

We simply write a test program to verify our conjecture :

/* * @Author: Adam Xiao * @Date: 2021-03-22 16:48:27 * @LastEditors: Adam Xiao * @LastEditTime: 2021-03-25 16:20:37 * @FilePath: /test/test_Adam.c */
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
struct test {

int a;
short b;
char c;
};
int main(void)
{

int fd = open("/dev/Adam", O_RDONLY);
if (fd < 0) {

puts("open fail");
return -1;
}
struct test t = {
100, 20, 10};
printf("tt:%p\n", &t);
int ret = ioctl(fd, 0x11223344, &t);
if (ret != 0) {

puts("ioctl fail");
} else {

printf("ret = %d, t.a = %d, t.b = %d, t.c = %d\n", ret, t.a, t.b, t.c);
}
close(fd);
return 0;
}

You can see , We opened the driver we added /dev/Adam This equipment , And gave him a ioctl. Look at this time , What are the results .

# ./test_Adam
tt:0xbeecdbd0
ret = 0, t.a = 111, t.b = 222, t.c = 33
# dmesg
[xyy]test driver init
driver information:[xyy] Open called!!!
driver information:cmd = 0x11223344
driver information:arg:beecdbd0
driver information:ret = 0, t.a = 100, t.b = 20, t.c = 10
driver information:[xyy] close called!!!

You can see , When we open Just load your own driver ,/dev/Adam When this device , The kernel does print Open Called!!!. As we expected . These functions are indeed registered successfully , More specifically read And other functions can register , In depth knowledge will not be carried out here , You can check it by yourself file_operations This structure , All operations that can be performed on the equipment , There are corresponding functions here that can be registered .

5.2 copy_from_user and copy_to_user

Careful students may have noticed , With these two functions at the driver level, we have to convert them , I don't know what it means .

Here comes another important concept of driver , Kernel state cannot directly access the address of application state data , So what to do ? The kernel provides copy_from_user and copy_to_user These two interfaces , The function is to transfer data in kernel state and user state . It's not going to be specific here , And what we often use strcpy The role of is actually similar , It's nothing more than moving data from kernel state to user state .

6. Overview to be done later

Completed the above example , In fact, the driver has been registered in the system , How to open in application mode , How to set in application status , How to pass it to the kernel , Then how does the kernel return what it wants to pass to the application . This process is a simple demonstration of , But most drivers actually do things like this , There are more factory interfaces called inside , The completed business is just more complex .

For example, lighting a light , Or the data read from an interface should be returned . So it's all in file_operations Register the corresponding action in , Whether it's ioctl Let it be ,read Let it be . Register the corresponding function , Then realize the function , adopt copy_to_user Passed to the application state .

7. Reference documents

  1. MODULE_AUTHOR macro ( Two )

  2. The difference between miscellaneous equipment and character equipment

copyright:author[I'm the Ming Hai bu],Please bring the original link to reprint, thank you. https://en.javamana.com/2022/142/202205211829323646.html