目录
一、Linux驱动分离与分层
为了方便驱动的编写,提高软件的重用以及跨平台性能,Linux中提出了驱动的分层与分离
1.1、驱动的分隔与分离
分为主机驱动和设备驱动,中间又统一的API进行连接。主机控制器驱动一般是半导体厂商写的,设备驱动是在linux驱动框架下编写的,这个就是Linux 中的总线(bus)、驱动(driver)和设备(device)模型
1.2、驱动的分层
二、驱动 - 总线 - 设备 模型
根据驱动的分离与分层就衍生出了总线(bus) - 驱动(driver) - 设备(device)驱动框架。总线代码一般不用我们编写,linux内核提供的。我们只需要编写驱动和设备,当向总线注册驱动的时候,总线会从现有的所以设备中查找,看看哪个设备和此驱动匹配,同理当向总线注册设备的时候总线也会在现有的驱动中查看是否与与之匹配的驱动。
驱动:是具体的设备驱动,读、改、擦、写
设备:设备属性,比如IIC设备就有IIC器件地址、速度等
2.1 总线
总线数据类型为 bus_type,使用 bus_register 向内核注册总线,主要作用就是完成驱动与设备的匹配。Linux中总线种类较多,可在根文件系统的 /sys/bus 目录下查看
struct bus_type {
const char *name;
const char *dev_name;
struct device *dev_root;
struct device_attribute *dev_attrs; /* use dev_groups instead */
const struct attribute_group **bus_groups;
const struct attribute_group **dev_groups;
const struct attribute_group **drv_groups;
int (*match)(struct device *dev, struct device_driver *drv);
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev);
int (*remove)(struct device *dev);
void (*shutdown)(struct device *dev);
int (*online)(struct device *dev);
int (*offline)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
const struct dev_pm_ops *pm;
const struct iommu_ops *iommu_ops;
struct subsys_private *p;
struct lock_class_key lock_key;
};
2.2 驱动
总线数据类型为 device_driver,使用 driver_register 向内核注册驱动。驱动跟设备匹配以后驱动里面的 probe 函数就会执行。可在根文件系统的 /sys/bus/具体总线/drivers 目录下查看
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;
};
2.3 设备
总线数据类型为 device,使用 device_register 向内核注册设备。驱动跟设备匹配以后驱动里面的 probe 函数就会执行。可在根文件系统的 /sys/bus/具体总线/devices目录下查看
struct device {
struct device *parent;
struct device_private *p;
struct kobject kobj;
const char *init_name; /* initial name of the device */
const struct device_type *type;
struct mutex mutex; /* mutex to synchronize calls to
* its driver.
*/
struct bus_type *bus; /* type of bus device is on */
struct device_driver *driver; /* which driver has allocated this
device */
...
/* arch specific additions */
struct dev_archdata archdata;
struct device_node *of_node; /* associated device tree node */
struct fwnode_handle *fwnode; /* firmware device node */
dev_t devt; /* dev_t, creates the sysfs "dev" */
};
三、platform平台驱动模型
根据 驱动 -- 总线 -- 设备驱动模型,IIC、SPI、USB这样实实在在的总线是完全匹配,但是有一些外设是没法归结为具体的总线的,比如SOC内部的定时器、RTC、LCD等。为此linux内核创造了一个虚拟的平台驱动模型:platform驱动 -- 总线 -- platform设备
3.1 platform总线
对于platform平台而言,platform总线也是bus_type类型。platform_match 负责platform驱动和设备的匹配,在 platform_bus_init ---> bus_register中完成了向内核注册 platform总线
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
EXPORT_SYMBOL_GPL(platform_bus_type);
int __init platform_bus_init(void)
{
int error;
early_platform_cleanup();
error = device_register(&platform_bus);
if (error)
return error;
error = bus_register(&platform_bus_type);
if (error)
device_unregister(&platform_bus);
of_platform_register_reconfig_notifier();
return error;
}
3.2 platform驱动
platform驱动是在 struct device_driver 的基础上派生出来的,增加了一些platform自己的属性,使用 platform_driver_register 向内核注册platform驱动,如果platform驱动跟platform设备匹配成功以后,platform_driver 中的probe就会执行。
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 of_device_id *of_match_table;
// ---> const char *name;
const struct platform_device_id *id_table;
bool prevent_deferred_probe;
};
3.3 platform设备
platform驱动是在 struct device 的基础上派生出来的,增加了一些platform自己的属性。在无设备树的时候,需要使用 platform_device_register 向内核注册 platform设备在有设备树的时候,只需要修改设备树即可。如果platform驱动跟platform设备匹配成功以后,platform_driver 中的probe就会执行。
struct platform_device {
const char *name;
int id;
bool id_auto;
struct device dev;
u32 num_resources;
struct resource *resource;
const struct platform_device_id *id_entry;
char *driver_override; /* Driver name to force a match */
/* MFD cell pointer */
struct mfd_cell *mfd_cell;
/* arch specific additions */
struct pdev_archdata archdata;
};
3.4 platform匹配过程
驱动和设备匹配是通过 bus->match函数来完成的,platform总线下的match函数就是 platform_match
/**
* platform_match - bind platform device to platform driver.
* @dev: device.
* @drv: driver.
*
* Platform device IDs are assumed to be encoded like this:
* "<name><instance>", where <name> is a short description of the type of
* device, like "pci" or "floppy", and <instance> is the enumerated
* instance of the device, like '0' or '42'. Driver IDs are simply
* "<name>". So, extract the <name> from the platform_device structure,
* and compare it against the name of the driver. Return whether they match
* or not.
*/
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(再尝试通过acpi来匹配) */
if (acpi_driver_match_device(dev, drv))
return 1;
/* Then try to match against the id table(根据driver下的id_table来匹配dev) */
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;
/* fall-back to driver name match */
/*最终的就是比较字符串,也就是比较 platform_device->name 和 platform_driver->driver->name
*这是大多数无设备树情况下的匹配使用方式*/
return (strcmp(pdev->name, drv->name) == 0);
}
有设备树的时候,匹配过程为:
of_driver_match_device(dev, drv)
---> of_match_device //of_match_table 为 platform驱动下的 device_driver ---> of_match_table
static inline int of_driver_match_device(struct device *dev,const struct device_driver *drv)
{
return of_match_device(drv->of_match_table, dev) != NULL;
}
/*
* Struct used for matching a device
*/
struct of_device_id {
char name[32];
char type[32];
char compatible[128];
const void *data;
};
四、platform平台驱动实验程序编写
4.1 无设备树
实验例程以led点灯为例,需要分为led设备和led驱动两部分来写,led设备中包含了led的所有资源,也就是所用IO的所以地址信息,这些地址信息会提供给led驱动使用。也就是在led驱动中,会使用函数 platform_get_resource 从led设备中获取这些资源(地址信息)
struct resource *platform_get_resource(struct platform_device *dev,
unsigned int type, //资源类型
unsigned int num)//资源索引
程序源码:
https://github.com/denghengli/linux_driver/tree/master/18_led_platform
使用示例:
4.2 有设备树
有设备树的时候,设备是由设备树描述的,因此不需要再向总线注册设备,而是直接修改设备树,然后编写驱动。驱动与设备匹配成功以后,设备信息就会从设备树节点转为platform_device结构体,实验例程以led点灯为例:
程序源码:
https://github.com/denghengli/linux_driver/tree/master/19_led_dtsplatform
使用示例: