Description of platform driver framework under Linux

Article directory

  • Driver separation and layering
  • Introduction to platform model

If you want to write complex peripheral drivers, the Linux system must consider the reusability of the driver, so the software idea of driver separation and layering is proposed. The platform device driver was born under this idea, which is also called Drivers for platform devices.
For a mature, large, and complex operating system like Linux, code reusability is very important, otherwise there will be a lot of meaningless duplicate code in the Linux kernel.

Separation and layering of drivers

The driver written in the simplest way should look like the figure below.

But the device drivers are all the same, so there is no need to write one for each platform, so a unified interface is provided so that each device can be accessed through this interface.

There are many types of device drivers in practice, and the overall architecture should be as shown in the figure below.

This is the bus, driver and device model in Linux, that is, the driver is separated, and the bus is responsible for connecting the driver and the device. When we register a driver with the system, the bus will search the devices on the right to see if there is a matching device, and if so, connect the two. Similarly, when a device is registered to the system, the bus will search the driver on the left to see if there is a matching device, and if so, it will be contacted. A large number of drivers in the Linux kernel use bus, driver and device modes.
Let’s briefly introduce the driver layering. The input subsystem is responsible for managing all input-related drivers, including keyboard, mouse, touch, etc. The lowest layer is the device original driver, which is responsible for obtaining the original value of the input device and reporting the obtained input events. Give the input core layer. The input core layer will handle various IO models and provide a set of file_operations operations. When writing the input device driver, you only need to handle the reporting of input events. As for how to handle these reported input events, it is for the upper layer to consider.

Introduction to platform model

The modes of bus, driver and device are mentioned above, but some peripherals in SOC do not have the concept of bus. Linux proposes the platform virtual bus, correspondingly
There are platform_driver and platform_device.
The Linux system kernel uses the bus_type structure to represent the bus. This structure is defined in the file include/linux/device.h.

int (*match)(struct device *dev,struct device_driver *drv);

The match function in the structure is to complete the matching between the device and the driver. The bus uses the match function to find the corresponding driver according to the registered device, or to find the corresponding device according to the registered driver, so each bus must implement this function.
The platform bus is a specific instance of bus_type and is defined in the file drivers/base/platform.c. The platform bus is defined as follows.

struct bus_type platform_bus_type = {<!-- -->
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};

platform_bus_type is the platform platform bus, where platform_match is the matching function. This function is also defined in the file platform.c. The function content is as follows.

static int platform_match(struct device *dev, struct device_driver *drv)
{<!-- -->
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);

/* When driver_override is set, only bind to the matching driver */
if (pdev->driver_override)
return !strcmp(pdev->driver_override, drv->name);

/* Attempt an OF style match first */
if (of_driver_match_device(dev, drv))
return 1;

/* Then try ACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1;

/* Then try to match against the id table */
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;

/* fall-back to driver name match */
return (strcmp(pdev->name, drv->name) == 0);
}

This function defines four ways of matching drivers and devices. The first is of type matching, which is the matching method used by the device tree. The of_driver_match_device function is defined in the file include/linux/of_device.h. There is a member variable named of_match_table in the device_driver structure. This member variable saves the compatible match table of the driver. The compatible attribute of each device node in the device tree will be compared with all members in the of_match_table table to see if there are the same ones. Entry, if any, indicates that the device matches this driver. The probe function will be executed after the device and driver are successfully matched. The second is the ACPI matching method. The third type is id_table matching. Each platform_driver structure has an id_table member variable, which stores a lot of id information. These id information stores the driver types supported by the platform driver. The fourth method is to directly compare the name fields of the driver and device. If they are equal, the match is successful.
The platform_driver structure represents the platform driver. This structure is defined in the file include/linux/platform_device.h, as shown below.

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 (*resume)(struct platform_device *);
struct device_driver driver;
const struct platform_device_id *id_table;
bool prevent_deferred_probe;
};

When the driver and device are successfully matched, the probe function will be executed. Generally, the driver provider will write it. If you want to write a brand new driver, then the probe needs to be implemented by yourself.
The driver member is the device_driver structure variable. Object-oriented thinking is widely used in the Linux kernel. Device_driver is equivalent to the base class and provides the most basic driver framework. plaform_driver inherits this base class, and then adds some unique member variables based on it.
The device_driver structure is defined in include/linux/device.h, and its prototype is as follows.

struct device_driver {<!-- -->
const char *name;
struct bus_type *bus;

struct module *owner;
const char *mod_name; /* used for built-in modules */

bool suppress_bind_attrs; /* disables bind/unbind via sysfs */

const struct of_device_id *of_match_table;
const struct acpi_device_id *acpi_match_table;

int (*probe) (struct device *dev);
int (*remove) (struct device *dev);
void (*shutdown) (struct device *dev);
int (*suspend) (struct device *dev, pm_message_t state);
int (*resume) (struct device *dev);
const struct attribute_group **groups;

const struct dev_pm_ops *pm;

struct driver_private *p;
};

Among them, of_match_table is the matching table used by the driver when using the device tree. Each matching item is of_device_id structure type. This structure is defined in the file include/linux/mod_devicetable.h, as shown below.

struct of_device_id {<!-- -->
char name[32];
char type[32];
char compatible[128];
const void *data;
};

For the device tree, the compatible attribute value of the device node is compared with the compatible member variable of each item in of_match_table. If there is an equal value, it means that the device and this driver are successfully matched.
When writing a platform driver, first define a platform_driver structure variable, and then implement each member variable in the structure, focusing on implementing the matching method and probe function. When the driver and device are successfully matched, the probe function will be executed, and the specific driver program is written in the probe function.
After defining and initializing the platform_driver structure variable, you need to call the platform_driver_register function in the driver entry function to register a platform driver with the Linux kernel. The platform_driver_register function prototype is as follows.

int platform_driver_register (struct platform_driver *driver)

Uninstall the platform driver when exiting. The platform_driver_unregister function prototype is as follows.

void platform_driver_unregister(struct platform_driver *drv)

After the above introduction, the platform driver framework is roughly as follows.

struct cdev cdev;

static int xxx_open(struct inode *inode, struct file *filp)
{<!-- -->
...
return 0;
}

static ssize_t xxx_write(struct file *filp, const char __user *buf,size_t cnt, loff_t *offt)
{<!-- -->
...
return 0;
}

static struct file_operations xxx_fops = {<!-- -->
.owner = THIS_MODULE,
.open = xxx_open,
.write = xxx_write,
};

static int xxx_probe(struct platform_device *dev) //This function is executed after the driver and device are successfully matched. The content of the original init function is placed here.
{<!-- -->
...
cdev_init( & amp;xxxdev.cdev, & amp;xxx_fops); /* Register character device driver */
return 0;
}

static int xxx_remove(struct platform_device *dev) //Executed when closing the platform device driver, the content of the original exit function is placed here
{<!-- -->
...
cdev_del( & amp;xxxdev.cdev); /* Delete cdev */
return 0;
}

static const struct of_device_id xxx_of_match[] = {<!-- -->
{<!-- -->.compatible = "xxx-gpio" },
{<!-- --> /* Sentinel */ }
};

static struct platform_driver xxx_driver = {<!-- -->
.driver = {<!-- -->
.name = "xxx",
.of_match_table = xxx_of_match,
},
.probe = xxx_probe,
.remove = xxx_remove,
};

static int __init xxxdriver_init(void)
{<!-- -->
platform_driver_register( & amp;xxx_driver);
return 0;
}

static int __exit xxxdriver_exit(void)
{<!-- -->
platform_driver_unregister( & amp;xxx_driver);
return 0;
}

module_init(xxxdriver_init);
module_exit(xxxdriver_exit);
MODULE_LICENSE("GPL");

The platform_device structure is defined in the file /include/linux/platform_device.h. If the kernel supports device tree, there is no need to use platform_device to describe the device, because the device tree is used to describe it instead.

References:
I.MX6U Embedded Linux Driver Development Guide V1.5 – Punctual Atomic