I'm the Ming Hai bu 2022-05-22 12:45:08 阅读数:94
ioctl
, Interaction between application state and kernel state menuconfig
Kernel source code of .( The main difference between online and offline kernel source code packages is menuconfig After that, a .config
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 .
# 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
The total number of changes needed here is Three places :
obj-m
hinder .o
File name of , Change to your own driven .c
Name of file ;KERNELDIR
Change to linux The folder address of the kernel source code , remember , If menuconfig
Yes .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 .
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 .
/* * @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 .
The above driver can simply run , It shows that our framework is correct . Let's explain it briefly , Some special interfaces in the kernel .
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 .
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 .
misc_register
and misc_deregister
Here are two concepts , Miscellaneous equipment (misc_device) And character devices (char_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 .
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 .
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
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 .
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 .
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 .
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 .
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 .
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