Wei Dongshan Linux driver entry experiment class (5) LED driver --- driver layering and separation, platform bus model

foreword

(1) The code of how to write the LED driver has been introduced in detail. If Wei Dongshan has already understood the LED driver in the Linux driver entry experiment class (4) , the module experiments behind the driver entry experiment class are actually not much different from the operation of a single-chip microcomputer. I won't waste any more time explaining.
(2) This article mainly explains the layering and separation of drivers, and the platform bus model.
(3) For teacher Wei Dongshan's code, I made fine-tuning, because his code was written in a hurry, so I felt that some places felt a little redundant, so I made adjustments on my own initiative. But it turns out that his part has not been deleted. If you think Wei Dongshan's is better than mine, you can comment out the part I fine-tuned. (I will explain the fine-tuning part)
(4) Code warehouse: gitee warehouse ; github warehouse
(5)Note: You can download the code in my warehouse and read this article to understand it better. The code in my warehouse still has detailed comments, and I feel that this article is too redundant. You can learn from the documentation of my warehouse code and punctual atoms

driver layering

What is driver layering

(1) Through the previous study, we will find that Linux development divides a program into two layers, the application layer and the driver layer. But when we write the driver layer program, we will find that the driver is still related to the specified pin and the development board.
(2) At this time, someone will definitely say, I just need you to change which array. Is there any difficulty in this?
(3) It is not difficult, but for a mature, large and complex operating system like Linux, code reusability is very important, otherwise there will be a lot of meaningless repetitive codes in the Linux kernel. Especially the driver, because the driver occupies a large part of the Linux kernel code, if the driver is not managed and the repeated code is allowed to increase arbitrarily, then the number of files in the Linux kernel will become unacceptably large in a short time.
(4) Some people may say that the programs I wrote before are not repeated. But it is just to change the definition of the gpios[] structure of the driver. However, you need to know that when developing a large project, you store device information in the driver. Every adjustment needs to open the corresponding driver. If we accidentally move somewhere in the driver and a bug occurs, it will be very troublesome to check. Therefore, in order to keep the bloated Linux system unchanged, and at the same time, for the sake of security, Linux decided to split the driver into a driver and a hardware description program. Once the driver is written, it basically does not need to be opened again.

insert image description here

(5) Now we know, after the stripping of the driver and the hardware description program. Linux then proposed a platform bus model. We mount the driver and hardware description information on this bus.
(6) With this bus, we can first mount some information on our device on the bus. Then give these devices a name.
(7) When we need to use a specified device, the driver can find the device information by giving the device a name. Once the driver and device are matched, the specified program can be executed.
(8) What are the benefits of doing this? In this way, our driver can be transplanted directly without being limited to any device. After we get a development board, we can directly transplant the driver, and then write the device description information, and the driver code hardly needs to be adjusted.

insert image description here

How the device is mounted on the platform bus

As we said above, Linux invented the platform bus, we only need to mount the device and driver on the bus, and if they match, the program will be executed automatically. So how is the device mounted on the platform bus.

platform_device_register()

(1) The platform_device_register() function can mount device information on the platform bus. We only need to pass in the struct platform_device structure type pointer. (2) The following is the definition of the platform_device() structure. Although there are many parameters, only name , id , resource , num_resources , and dev
need to be paid attention to . <1>name: The name of the device, used to match the driver. Only the same name can match successfully <2>id: ID is used to distinguish if the device name is the same (by adding a number behind to represent different devices, because sometimes there is such a requirement) <3>resource: resource structure array. The device information we need to write is written in this array. <4>num_resources: the number of resource structures. Indicates how many device structures there are, which is actually a macro definition #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) <5>dev: the device object associated with the platform device . This only needs to know that a function pointer must be passed to the .release parameter. This function is called when the device is unmounted.




// platform 设备结构体
struct platform_device led_device = {
    
    
	.name = "led_device",  //platform 设备的名字, 用来和 platform 驱动相匹配。 名字相同才能匹配成功
	.id = -1,   // ID 是用来区分如果设备名字相同的时候(通过在后面添加一个数字来代表不同的设备, 因为有时候有这种需求)
	.resource = led_resource,  //指向一个资源结构体数组。 一般包含设备信息
	.num_resources = ARRAY_SIZE(led_resource),  //资源结构体数量
	.dev = {
    
    
		.release = LED_release,
		}
};

platform_device_unregister()

(1) This is used to offload the device from the platform bus. When this device is not needed, we can uninstall it.
(2) Same as platform_device_register(), only need to pass in the struct platform_device structure type pointer.

Entry and exit of device programs

(1) The device program, like the driver program, is loaded and unloaded using insmod and rmmod. The program entry is defined using the module_init() macro, and the program exit is also defined using module_exit().
(2) He must also use the call MODULE_LICENSE ("GPL"); the specified module is the GPL agreement

How the driver is mounted on the platform bus

platform_driver_register()

(1) This function only needs to pass in a static struct platform_driver type structure pointer.
(2) The structure of the driver is much easier to understand than the structure of the device.
<1> We only need to know that the ".name" in the platform_driver structure ".driver" is exactly the same as the ".name" in the platform_device structure.
<2> When the device and driver are matched, we will execute the probe function. If the device and driver are disconnected, the remove function is executed. You can understand it this way, a man and a woman, a boy and a girl are married, then a marriage certificate will be issued (execute the probe function). One day, the two of them broke up because of some things, and they will be given a divorce certificate (execute the remove function).

static struct platform_driver led_driver = {
    
    
	.driver		= {
    
    
		.name	= "led_device",   //根据这个名字,找到设备
		.owner = THIS_MODULE,
	},
	.probe		= led_drver_probe,   //注册平台之后,内核如果发现支持某一个平台设备,这个函数就会被调用。入口函数
	.remove		= led_drver_remove,  //出口函数
};

platform_driver_unregister()

This is used to uninstall the driver on the platform. Like platform_driver_register(), a static struct platform_driver structure pointer is passed in.

The difference from the previous blog

The hardware information is different

(1) The main thing here is to put the hardware description information originally placed in the gpios[] array into another file. Let the driver be separated from the hardware platform.
(2) The original hardware description is stored in an array, but the current hardware description is placed in a structure pointer, and then the device information is obtained through the platform bus in Linux.

/**********   原来的硬件描述  **********/
static struct gpio_desc gpios[] = {
    
    
    {
    
    131, "led0" },  //引脚编号,名字
};
/**********   现在的硬件描述  **********/
static struct gpio_desc *gpios;   //描述gpio

The initializer is different

(1) In the previous blog, our driver initialization functions are placed in the module_init() macro.
(2) But it is different now. The function in the module_init() macro only does one thing, which is to register the static struct platform_driver structure on the platform bus.

/**********   原来的module_init()中的函数任务  **********/
static int __init gpio_drv_init(void)
{
    
    
	//申请GPIO
	//将GPIO设置为输出
	//注册字符驱动程序
	//创建类
	//创建设备名/dev目录下的设备名
}
/**********   现在的module_init()中的函数任务  **********/
//注册时候的入口函数
static int __init led_drver_init(void)
{
    
    
	int ret = 0;
	// platform驱动注册到 Linux 内核
	ret = platform_driver_register(&led_driver);  //注意,这里是driver表示是驱动
	if(ret<0)
	{
    
    
		printk("platform_driver_register error \n");
		return ret;
	}
	printk("platform_driver_register ok \n");
	return ret;
}

Initializer execution conditions are different

(1) It turns out that we initialize the program and only need to load the driver.
(2) It is different now, we need to register matching device programs and drivers, and the initialization program probe function will be executed.
(3) Note that there is no order in the registration of device programs and drivers. Whoever you sign up with first.

Obtaining GPIO quantity information is different

(1) Some people here may have questions about pdev, what is this? This thing is very simple, pdev is the pointer to the matched device struct platform_device structure.
(2) After the driver and device are matched, execute the probe function. And the incoming value of this function struct platform_device *pdev is the matched device struct platform_device structure pointer.
(3) Because the num_resources of the struct platform_device structure records the number of resource structures. So you can get the number of GPIO through pdev -> num_resources.
(4) When we look at teacher Wei Dongshan's code, we will find that it is very long and troublesome. At the beginning, I thought so too. It can be solved by naming a command. Why is it so long? After thinking about it briefly. The code is so long, it still works.
(5) Let's look at the platform_get_resource() function, which is used to return the GPIO information of the GPIO whose flags in the resource resource in the struct platform_device structure are marked as IORESOURCE_IRQ. When the flags of our GPIO are marked as IORESOURCE_IRQ, a pointer will be returned to point here.
(6) If some GPIO flags of our device are not marked as IORESOURCE_IRQ, then they will not be counted.

/**********   原来获取GPIO数量  **********/
int count = sizeof(gpios)/sizeof(gpios[0]);  //统计有多少个GPIO
/**********   现在获取GPIO数量(作者的方法)  **********/
count = pdev -> num_resources;
/**********   现在获取GPIO数量(韦东山老师的方法)  **********/
while (1)
{
    
    
	/* 下面这9行,是用于统计有多少个GPIO的
	 * dev:一个指向 platform_device 结构的指针,表示要获取资源信息的设备。
	 * type:一个无符号整数,表示要获取的资源类型。在 Linux 内核中,资源类型使用常量来表示,
	        例如 IORESOURCE_MEM 表示内存资源,IORESOURCE_IRQ 表示中断资源等。你可以根据需要选择适当的资源类型。
	 * num:一个无符号整数,表示要获取的资源的索引号。在一个设备中可能存在多个相同类型的资源,通过索引号可以区分它们。
	 * 返回值:返回一个指向 resource 结构的指针,表示获取到的资源信息。
	           resource 结构包含了资源的起始地址、大小等信息。如果没有找到指定的资源,函数将返回 NULL。
	*/
	led_resource = platform_get_resource(pdev, IORESOURCE_IRQ, count);
	if (led_resource)
	{
    
    
		count++;
	}
	else
	{
    
    
		break;
	}
}

The device structure gpios space allocation is different

(1) In the original code, our gpios is defined as an array gpios[]. So when we write device information in this array, this array will automatically change the size of the space.
(2) But the device information here is not in the driver file, so we need to use dynamic allocation of memory. After we know how many GPIOs there are, call the memory dynamic allocation function to obtain a space, and then return the first address pointer of this space to gpios.
(3) One thing to note is that the dynamic memory allocation of the driver cannot use malloc, but kmalloc. This is the same reason that the driver cannot use the printf function, but can only use the printk function.

/**********   原来gpios空间分配  **********/
static struct gpio_desc gpios[] = {
    
    
    {
    
    131, "led0" },  //引脚编号,名字
};
/**********   现在gpios空间分配  **********/
static struct gpio_desc *gpios;   //描述gpio
/* 作用 :  kmalloc是Linux内核中的一个内存分配函数,用于在内核空间中动态分配内存。
 *        它类似于C语言中的malloc函数,但是在内核中使用kmalloc而不是 malloc,因为内核空间和用户空间有不同的内存管理机制。
 * size : 要分配的内存大小,以字节为单位。
 * flags : 分配内存时的标志,表示内存的类型和分配策略,是一个 gfp_t 类型的值。常常采用GFP_KERNEL
 *          GFP_KERNEL是内存分配的标志之一,它表示在内核中以普通的内核上下文进行内存分配。
 * 返回值 : 如果内存分配成功,返回指向分配内存区域的指针。如果内存分配失败(例如内存不足),返回NULL。
*/
gpios = kmalloc(count * sizeof(struct gpio_desc), GFP_KERNEL); 

Device structure gpios hardware information acquisition is different

(1) It turns out that we can directly write hardware information into the gpios[] array.
(2) Because we have written the hardware information into the device program now, we need other methods to obtain it. There are two ways to get hardware information from the device program.
<1> Obtain hardware information directly through the pedv pointer.
<2> Obtained through the platform_get_resource() function.
(3)
<1>Comparing the code below, we will find that led_resource and pdev->resource[i] are the same thing.
<2> It can be said so, but there are still certain differences. If the flags of pdev->resource[i] is not IORESOURCE_IRQ, then it will not be extracted into led_resource.

/**********   原来gpios获得硬件信息  **********/
static struct gpio_desc gpios[] = {
    
    
    {
    
    131, "led0" },  //引脚编号,名字
};
/**********   现在gpios获得硬件信息  **********/
//通过pedv指针直接获得硬件信息。
gpios[i].gpio = pdev->resource[i].start;
sprintf(gpios[i].name, "%s", pdev->resource[i].name);   //将platform_device.resource.name传递给gpios[i].name

//通过platform_get_resource()函数获得。
struct resource *led_resource;
led_resource = platform_get_resource(pdev, IORESOURCE_IRQ, i);  //从节点里面取出第i项	
gpios[i].gpio = led_resource->start; 						   //将需要操作的IO号传递给gpios[i].gpio
sprintf(gpios[i].name, "%s", led_resource->name);   //将platform_device.resource.name传递给gpios[i].name


Guess you like

Origin blog.csdn.net/qq_63922192/article/details/131836779