Three Linux character device driver writing methods-2: bus device driver framework

This article introduces the Linux bus device driver framework. The focus is to understand the whole framework, especially to feel the idea of ​​separation and abstract methods . Members of many structures do not need to go into the details of how to realize or what their functions are. I will introduce them in the future discussed in depth in the article.

References:

Embedded Linux driver learning-6.platform bus device driver model

Embedded Linux driver learning-5. The idea of ​​layered separation of drivers

1. Separation of mind

Review the first article, the simplest driver framework:

As follows, directly write resources (IO pins) in the driver program

static unsigned long gpio_va;
 
#define GPIO_OFT(x) ((x) - 0x56000000)
#define GPFCON  (*(volatile unsigned long *)(gpio_va + GPIO_OFT(0x56000050)))
#define GPFDAT  (*(volatile unsigned long *)(gpio_va + GPIO_OFT(0x56000054)))

Friends who have a single-chip microcomputer foundation, or who write a lot of c programs may find the flaws of this writing method:

It’s okay when the program is simple, but when the program is complicated and the hardware needs to be replaced , it will become difficult to modify. Therefore, it is necessary to separate the code of the hardware-related part, which is called separation , part of the code in the hardware layer, and part of the code in the driver layer. It's better in one file, but you still need to recompile the whole program if you make changes.

The bus device driver model is adopted in the Linux system, which perfectly solves these problems.

Introduce platform_device/platform_driver to separate "hardware resources" from "drivers" .

Also take the led driver as an example, we write two source files, led_drv.c represents the driver, led_dev.c represents the device (resource), compile and generate led_drv.ko and led_dev.ko two files, after insmod can be used normally, when When we need to modify the hardware, we only need to rmmod led_dev , and then reload, instead of unloading and modifying led_drv.

2. platform_device

static int led_dev_init(void)
{
	platform_device_register(&led_dev);
	return 0;
}

static void led_dev_exit(void)
{
	platform_device_unregister(&led_dev);
}

module_init(led_dev_init);
module_exit(led_dev_exit);

From the entrance, it is the same as the basic driver in the first article, except that the entrance has changed from register_chrdev to register platform_device.

led_dev in the platform_device_register(&led_dev) function is a platform_device structure

static struct platform_device led_dev = {
    .name         = "myled",
    .id       = -1,
    .num_resources    = ARRAY_SIZE(led_resource),
    .resource     = led_resource,
    .dev = { 
    	.release = led_release, 
	},
};

Structure prototype:

struct platform_device {
	const char	* name;
	u32		id;
	struct device	dev;
	u32		num_resources;
	struct resource	* resource;
};

The first item: name , take a name

The second item: id , the device instance number, or "-1" means there is only one instance.

The third item: dev , a device structure, where an empty function is implemented, no warning will be reported, and other members are not used. An article will be published later.

    .dev = { 
    	.release = led_release, 
	},
static void led_release(struct device * dev)
{
}

The fourth item: num_resources, the number of resources, ARRAY_SIZE(led_resource) returns the number of resources

The fifth item: resource , resource array

Use the resource structure to implement a resource array, and the content of the array is our hardware resources

start is the start, end is the end, flags is the resource type, my development board GPF4, GPF5, GPF6 are three led lights

GPFCON is 0x56000050, and GPFDAT is 0x56000054. Define the following resources. In fact, the first one can control all lights. This is written here to highlight the function and convenience of using resource arrays.

static struct resource led_resource[] = {
    {
        .start = 0x56000050,
        .end   = 0x56000050 + 8 - 1,
        .flags = IORESOURCE_MEM,
    },
    {
        .start = 4,
        .flags = IORESOURCE_IO,
    },
	{
        .start = 5,
        .flags = IORESOURCE_IO,
    },
	{
        .start = 6,
        .flags = IORESOURCE_IO,
    },

};

3. platform_driver

static int led_drv_init(void)
{
	platform_driver_register(&led_drv);
	return 0;
}

static void led_drv_exit(void)
{
	platform_driver_unregister(&led_drv);
}

module_init(led_drv_init);
module_exit(led_drv_exit);

From the same point of view, register platform_driver.

led_drv in the platform_driver_register(&led_drv) function is a platform_driver structure

struct platform_driver led_drv = {
	.probe		= led_probe,
	.remove		= led_remove,
	.driver		= {
		.name	= "myled",
	}
};

Structure prototype:

struct platform_driver {
	int (*probe)(struct platform_device *);
	int (*remove)(struct platform_device *);
	void (*shutdown)(struct platform_device *);
	int (*suspend)(struct platform_device *, pm_message_t state);
	int (*suspend_late)(struct platform_device *, pm_message_t state);
	int (*resume_early)(struct platform_device *);
	int (*resume)(struct platform_device *);
	struct device_driver driver;
};

In the future, an article will elaborate on this structure. Only a few basic members are needed here:

1.probe, probe , in this function to obtain resources, register character devices, create devices, etc.

static int led_probe(struct platform_device *pdev)
{
	struct resource		*res;
	led_num=0;

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	gpio_con = ioremap(res->start, res->end - res->start + 1);
	gpio_dat = gpio_con + 1;

	major = register_chrdev(0, "myled", &led_fops);
	cls = class_create(THIS_MODULE, "myled");

	while(1)
	{
		res = platform_get_resource(pdev, IORESOURCE_IO, led_num);
		if(res==NULL)
			break;
		led_num++;
		pin[led_num] = res->start;

		printk("led_probe, found led%d\n",led_num);

		class_device_create(cls, NULL, MKDEV(major, led_num), NULL, "led%d",led_num); /* /dev/led */
	}
	return 0;
}

Use platform_get_resource to obtain resources, pin values ​​are saved in the pin[] array, and then create device nodes in a loop, and create several led nodes if there are several led resources.

static int led_open(struct inode *inode, struct file *file)
{
	int minor;
	minor=iminor(inode);
	
	led_init(minor);

	return 0;	
}

static ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
	int val;
	
	int minor = iminor(file->f_path.dentry->d_inode);
	printk("write led%d\n",minor);

	copy_from_user(&val, buf, count); //	copy_to_user();

	led_ctl(minor,int);
	
	return 0;
}

The open and write in the file_operations structure are actually similar to the first article, the difference is that the initialization operation is encapsulated into the led_init() function, the control operation is encapsulated into the led_ctl() function, and the sub-device number is used to distinguish which One led to operate.

Get the minor device number from the inode structure:

minor=iminor(inode);

Get the minor device number from the file structure:

int minor = iminor(file->f_path.dentry->d_inode);

2.remove , this function means to remove and destroy the device

static int led_remove(struct platform_device *pdev)
{
	int i;
	i=0;
	for(i=0;i<led_num;i++)
		{
			class_device_destroy(cls, MKDEV(major, i));
		}
	class_destroy(cls);
	unregister_chrdev(major, "myled");
	iounmap(gpio_con);
	
	return 0;
}

3. driver, the name in this structure should correspond to the name in platform_device, so that platform_dev and platform_drv can be corresponding.

It is normal that insmod led_drv.ko does not respond

After insmod led_dev.ko, it prints out that three devices are found, indicating that platform_dev and platform_drv correspond to each other.

Then use the command to see if the device node is created successfully

Then you can control the three leds through the app.

./ledtest /dev/led1 on makes led1 bright, and so on, the source code of the application is basically the same as that in the first article.

This article is not very detailed, because the focus is actually to feel the layered thinking and understand some abstract structures in the linux system. In fact, manual work, you can find a lot of reference codes by searching in the source code. As the work progresses, you will naturally become familiar with these structures. In the future, I will also analyze some structures in depth.

Source code https://download.csdn.net/download/freestep96/86731971

Guess you like

Origin blog.csdn.net/freestep96/article/details/127141555