Linux内核驱动模型---平台总线驱动(platform)

前言

写这篇文章也是为了呼应上一篇文章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,
};
  1. 定义在drivers\base\platform.c
  2. 这里只是定义了一个全局变量,数据结构依旧是struct bus_type类型
  3. 其中主要关注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;
};
  1. 主要关注 resource,
  2. 这里也能看到有个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;
};
  1. pltfm drv也一样包含了drv类型,继承了drv的所有属性。
  2. 关注点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);
}
  1. 很明显的看到有注册一个bus类型,前面也说到主要关注bus的match方法 platform_match
  2. 由上图可以看到有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
}
  1. 关注platform_drv_probe函数,最主要的就是那个drv->probe。
  2. 在platform_drv_probe函数中假如pltfm drv的probe存在则执行probe,这个drv是pltfm drv。
  3. 所以最终还是会执行我们利用__platform_driver_register注册进来的驱动的probe函数也就是pltfm drv的probe。
  4. 所以我们在写平台总线驱动时肯定有这么一个环节指定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;
}
  1. module_platform_driver有兴趣的可以看看实现,比较简单,一堆宏最后module_init中就执行了platform_driver_register(mxsfb_driver)
  2. 这个驱动同时提供了2中匹配规则,按照pltfm bus的match匹配优先级,先匹配of的规则,假如of匹配失败,则采用id_table的匹配规则,这样子极大的灵活了pltfm dev的注册,设备不管你用哪种方式做你的匹配规则,我驱动都不用改。

总结

还是比较重要的一点,驱动框架的学习,应该抓住主题暂时忽略不相干的细节,像上面举的例子, i.MX23 和 i.MX28的LCD驱动大部分代码我都删除了,不要管他平台区分的内容,这个是frame buf的内容,这个不是这次学习的平台总线模型框架的内容,应忽略。后续假如用到了这个平台的frame buff 那么再去细细的去看。其他驱动模型亦如此,循序渐进的去了解。

Guess you like

Origin blog.csdn.net/weixin_41884251/article/details/113829620