平台总线驱动模型
前言
写这篇文章也是为了呼应上一篇文章Linux驱动模型,为了更好的理解设备驱动总线,这样的代码框架的好处。也算是这个驱动模型的一个最为广泛的实例运用。
platform平台总线驱动模型
平台总线驱动的框架模型,是建立在之前说到的设备总线驱动模型之上的。可以这么理解dev bus drv驱动模型更为广泛通用,而pltfm 总线是他的一种特殊情况,是它的子集。同样在Linux驱动中像i2c子系统或者spi子系统也都是 dev bus drv 驱动模型的子集,一种特殊情况。
平台总线,顾名思义,是与平台相关联的。设备总线驱动模型其划分出3大块就是为了更好的管理,利用代码将其联系起来,其中一大特点为,一个驱动可以对应多个设备。这样的好处驱动可以只写一份。但这也只是针对同一个平台,换个平台,换个cpu,你驱动又得写,但是你会发现其实驱动大部分不用修改,也就是寄存器的配置以及寄存器地址不一样而已。那有没有不同平台我也一份代码一个驱动搞定。诶,这平台总线他就来了。他来了,他来了。
回顾
在讲述平台总线前回顾下设备总线驱动模型,一个设备仅拥有一个驱动,一个驱动可以拥有多个设备,当设备和驱动注册时都会利用bus提供的match方法进行匹配,当匹配成功后,则会调用bus的probe方法,如果bus的probe不存在则调用drv的probe方法。细节详情见这篇文章Linux驱动模型
platform驱动类型介绍
有了上面回顾后的知识点我们再来看platform总线的细节。本文中与设备总线驱动模型相关的点细节就不在重提了,应该有设备总线驱动模型的印象后再来看本篇,理解起来会容易些。
pltfm bus
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
- 定义在drivers\base\platform.c
- 这里只是定义了一个全局变量,数据结构依旧是struct bus_type类型
- 其中主要关注platform_match bus的match接口后面详细介绍。
pltfm dev
//定义在\include\linux\platform_device.h
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;
};
----------------------------------------------
//定义在\include\linux\ioport.h
struct resource {
resource_size_t start; //一般描述资源的起始地址 如GPIO控制器的那一堆寄存器地址
resource_size_t end;//一般描述资源的结束地址
const char *name;
unsigned long flags;//标识是mem资源还是io资源 IORESOURCE_MEM IORESOURCE_IO
unsigned long desc;
struct resource *parent, *sibling, *child;
};
- 主要关注 resource,
- 这里也能看到有个dev成员变量,就拥有了设备总线驱动中设备的能力。继承了dev类型
pltfm drv
//定义在\kernel\include\linux\platform_device.h
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;
};
----------------------------------------------
//定义在\include\linux\mod_devicetable.h
struct platform_device_id {
char name[PLATFORM_NAME_SIZE];
kernel_ulong_t driver_data;
};
- pltfm drv也一样包含了drv类型,继承了drv的所有属性。
- 关注点struct platform_device_id 成员变量
注册涉及接口介绍
pltfm bus相关
dev和drv之间的联系就是靠bus来搭建的,所以必须先初始化bus,pltfm 模型也同样需要优先初始化bus
//\drivers\base\platform.c
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;
}
//前面dev bus drv模型中提到dev与drv匹配的规则就是利用bus的match方法来决定的
//在pltfm 驱动模型他的匹配条件是什么呢? 如下
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 */
//条件1,pltfm dev的driver_override与drv的name匹配,注意不是pltfm drv。优先级最高
if (pdev->driver_override)
return !strcmp(pdev->driver_override, drv->name);
/* Attempt an OF style match first */
//条件2,dev的of_node描述了设备树中的一个节点,这个节点的compatible属性(字符串)
//与drv->of_match_table中的compatible属性进行比较,也是比较字符串
//当然这个匹配内核必须支持设备树功能才会进,否则函数为空返回0,内核的条件编译。
//经常用
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 */
//platform_match_id--》if (strcmp(pdev->name, id->name) == 0)
//利用pltfm drv的id_table的name与pltfm dev的name进行cmp
//常用
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;
/* fall-back to driver name match */
//最后匹配规则pltfm dev的name与drv的name 进行cmp,注意不是pltfm drv。
return (strcmp(pdev->name, drv->name) == 0);
}
- 很明显的看到有注册一个bus类型,前面也说到主要关注bus的match方法 platform_match
- 由上图可以看到有5种条件,同时包含了优先级关系
pltfm bus的初始化函数有了,那么是谁来调用的呢,先说结论,细节不看,
是在linux 的启动流程里面执行的,也就是说设备一上电,pltfm bus 就已经创建注册好了。
platform_bus_init <--- driver_init <--- do_basic_setup <--- kernel_init_freeable <---
kernel_init <--- kernel_thread(kernel_init, NULL, CLONE_FS) <--- rest_init <---start_kernel <--- XXX.S
在汇编代码中执行start_kernel 从而调用到pltfm bus的初始化,linux的头一般可以认为从start_kernel开始。
pltfm dev drv 常用接口
我们常用的pltfm dev drv注册接口
platform_driver_register
platform_register_drivers
//定义在\include\linux\platform_device.h中
#define platform_driver_register(drv) \
__platform_driver_register(drv, THIS_MODULE)
extern int platform_device_register(struct platform_device *);
-------------------------------------------------
//定义在\drivers\base\platform.c
int __platform_driver_register(struct platform_driver *drv,
struct module *owner)
int platform_device_register(struct platform_device *pdev)
pltfm dev相关
int platform_device_register(struct platform_device *pdev)
{
device_initialize(&pdev->dev);//dev bus drv模型常规操作初始化
arch_setup_pdev_archdata(pdev);
return platform_device_add(pdev);
}
int platform_device_add(struct platform_device *pdev)
{
pdev->dev.bus = &platform_bus_type; //指定pltfm dev成员dev的bus类型
for (i = 0; i < pdev->num_resources; i++) {
struct resource *p, *r = &pdev->resource[i];
xxx
if (!p) {
if (resource_type(r) == IORESOURCE_MEM)
p = &iomem_resource;
else if (resource_type(r) == IORESOURCE_IO)
p = &ioport_resource;
}
if (p && insert_resource(p, r)) {
dev_err(&pdev->dev, "failed to claim resource %d\n", i);
ret = -EBUSY;
goto failed;
}
}
ret = device_add(&pdev->dev);//dev的注册函数
if (ret == 0)
return ret;
}
-------------------------------------------
//\kernel\resource.c,不清楚干了啥,放过,忽略,看主干
int insert_resource(struct resource *parent, struct resource *new)
pltfm drv相关
int __platform_driver_register(struct platform_driver *drv,
struct module *owner)
{
drv->driver.owner = owner;
drv->driver.bus = &platform_bus_type;
drv->driver.probe = platform_drv_probe;
drv->driver.remove = platform_drv_remove;
drv->driver.shutdown = platform_drv_shutdown;
return driver_register(&drv->driver);//drv的注册函数
}
//匹配成功后会调用bus的probe函数,假如bus的probe不存在则调用这个drv的probe,在之前dev bus drv模型中已经得到了这样的结论。
//正好pltfm的bus是没有probe的,那么就会调用drv的probe,
//而drv的probe在每个pltfm的drv注册时统一赋值,也就是这一句drv->driver.probe = platform_drv_probe;
//所以所有的pltfm drv都会调用platform_drv_probe。那么来瞧瞧这个probe。
static int platform_drv_probe(struct device *_dev)
{
struct platform_driver *drv = to_platform_driver(_dev->driver);
struct platform_device *dev = to_platform_device(_dev);
xxx
ret = dev_pm_domain_attach(_dev, true);
if (ret != -EPROBE_DEFER) {
if (drv->probe) {
ret = drv->probe(dev);//这句话其实是在调用pltfm drv的probe函数
if (ret)
dev_pm_domain_detach(_dev, true);
} else {
/* don't fail if just dev_pm_domain_attach failed */
ret = 0;
}
}
XXX
}
- 关注platform_drv_probe函数,最主要的就是那个drv->probe。
- 在platform_drv_probe函数中假如pltfm drv的probe存在则执行probe,这个drv是pltfm drv。
- 所以最终还是会执行我们利用__platform_driver_register注册进来的驱动的probe函数也就是pltfm drv的probe。
- 所以我们在写平台总线驱动时肯定有这么一个环节指定pltfm drv的probe。
static struct platform_driver xxx_driver = {
.driver = {
.name = DRIVER_NAME,
},
.probe = xxxx, //here指定环节
.remove = xxxx,
};
ret = platform_driver_register(&xxx_driver );
实例
在看例子前我们要明白一点我们是来理解pltfm 驱动框架的,要抓住核心,不相干的代码不看。接下来我会举一个飞思卡尔平台的frame buff的驱动。在这里和frame buff相关的代码就可以不看跳过,我们不关心他的实现。
//\drivers\video\fbdev\mxsfb.c
LCDIF driver for i.MX23 and i.MX28
module_platform_driver(mxsfb_driver); //驱动初始化,让初始化函数在系统启动后就自动调用。
static struct platform_driver mxsfb_driver = {
.probe = mxsfb_probe,//前面我们说了驱动初始化的时候肯定会赋值probe 这里就是
.remove = mxsfb_remove,
.shutdown = mxsfb_shutdown,
.id_table = mxsfb_devtype,//这里提供了id_table匹配
.driver = {
.name = DRIVER_NAME,
.of_match_table = mxsfb_dt_ids,//同时提供了of的匹配(设备树)
},
};
--------------------------------------------
//这个mxsfb_devdata才是同一份驱动中不同平台的差异部分,不同的平台取到的devdata是不一样的
static const struct mxsfb_devdata mxsfb_devdata[] = {
[MXSFB_V3] = {
.transfer_count = LCDC_V3_TRANSFER_COUNT,
.cur_buf = LCDC_V3_CUR_BUF,
.next_buf = LCDC_V3_NEXT_BUF,
.debug0 = LCDC_V3_DEBUG0,
.hs_wdth_mask = 0xff,
.hs_wdth_shift = 24,
.ipversion = 3,
},
[MXSFB_V4] = {
.transfer_count = LCDC_V4_TRANSFER_COUNT,
.cur_buf = LCDC_V4_CUR_BUF,
.next_buf = LCDC_V4_NEXT_BUF,
.debug0 = LCDC_V4_DEBUG0,
.hs_wdth_mask = 0x3fff,
.hs_wdth_shift = 18,
.ipversion = 4,
},
};
static const struct platform_device_id mxsfb_devtype[] = {
{
.name = "imx23-fb",
.driver_data = MXSFB_V3, //enum 0 索引用于在mxsfb_devdata取值
}, {
.name = "imx28-fb",
.driver_data = MXSFB_V4,//enum 1
}, {
/* sentinel */
}
};
static const struct of_device_id mxsfb_dt_ids[] = {
{
.compatible = "fsl,imx23-lcdif", .data = &mxsfb_devtype[0], },//虽然是设备树但data的值还是id_table里面的值
{
.compatible = "fsl,imx28-lcdif", .data = &mxsfb_devtype[1], },//同源的不同表示方式
{
/* sentinel */ }
};
static int mxsfb_probe(struct platform_device *pdev)
{
const struct of_device_id *of_id =
of_match_device(mxsfb_dt_ids, &pdev->dev);
struct resource *res;
...
int ret;
pdev->id_entry = of_id->data;//这几部分就是用来区分平台获取dev_data的
...
//res资源是属于pltfm dev的,这里驱动就是去拿pltfm dev的mem的0号资源的
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
//资源里面start和end其实就是这个寄存器的物理地址,拿到物理地址需要ioremap成虚拟地址才能使用。后面初始化的时候会赋值
host->base = devm_ioremap_resource(&pdev->dev, res);
...
platform_set_drvdata(pdev, host);
host->devdata = &mxsfb_devdata[pdev->id_entry->driver_data];//这几部分就是用来区分平台获取dev——data的
....
writel(0, host->base + LCDC_CTRL);//其他的初始和fb相关的就不写了。这里就有操作寄存器了
return ret;
}
- module_platform_driver有兴趣的可以看看实现,比较简单,一堆宏最后module_init中就执行了platform_driver_register(mxsfb_driver)
- 这个驱动同时提供了2中匹配规则,按照pltfm bus的match匹配优先级,先匹配of的规则,假如of匹配失败,则采用id_table的匹配规则,这样子极大的灵活了pltfm dev的注册,设备不管你用哪种方式做你的匹配规则,我驱动都不用改。
总结
还是比较重要的一点,驱动框架的学习,应该抓住主题暂时忽略不相干的细节,像上面举的例子, i.MX23 和 i.MX28的LCD驱动大部分代码我都删除了,不要管他平台区分的内容,这个是frame buf的内容,这个不是这次学习的平台总线模型框架的内容,应忽略。后续假如用到了这个平台的frame buff 那么再去细细的去看。其他驱动模型亦如此,循序渐进的去了解。