内核platform总线详解:定义、注册、匹配、使用示例

1、总线式设备驱动组织方式

1.1、总线

(1)外设与Soc连接都是通过接口,不同的接口本质就是通信协议不一样,随着内核要管理的设备越来越多,于是抽象出各种总线,希望将同一接口协议的设备连接在一起管理;
(2)总线分为物理总线和虚拟总线。物理总线是客观上存在的,比如usb总线,是真的有usb协议的设备连接在上面;虚拟总线是软件上模拟的,比如platform总线,没有哪种设备用的是platform协议,是内核为了统一用总线上方式管理设备虚拟出来的总线;
(3)使用总线的好处:实现数据和方法的分离,设备里包含了数据,驱动是硬件操作的方法。比如LED设备,一个设备可能有好几个LED,不同的LED设备之间寄存器数目、操作方法是一样的,不一样的是寄存器的基地址不同、连接的gpio口不同,于是我们可以把不同的数据放在总线上设备信息里(描述设备的结构体里有描述资源),然后传给LED驱动,这样就可以实现一个驱动适配几个设备。

1.2、设备

(1)内核中struct device结构体是硬件设备在内核驱动框架中的抽象,是硬件设备的共性部分;
(2)通常struct device结构体不会单独使用,会被包含在一个表示具体设备的结构体里,比如:struct platform_device结构体;
(3)用device_register()函数向内核驱动框架注册一个设备;

1.3、驱动程序

(1)struct device_driver是驱动程序在内核驱动框架中的抽象;
(2)通常struct device_driver结构体不会单独使用,会被包含在一个表示具体驱动的结构体里,比如:struct platform_driver结构体;
(3)用driver_register()函数向内核驱动框架注册一个驱动;

2、platform总线介绍

(1)CPU与外部通信的2种方式:地址总线式连接和专用接口式连接。专用接口:比如USB接口,有USB设备接在USB接口上,再把多个USB设备挂载在USB总线上;地址总线式:比如操作LED灯,其实就是操作一个GPIO口,GOIO口的寄存器CPU是可以像地址一样直接访问的,不需要接口的概念也很方便读写;
(2)platform总线是虚拟总线,是软件层虚拟出来方便管理设备的,主要是管理类似LED灯这种比较简单,不需要专用接口的设备;
(3)如果没有platform总线,那一部分设备是总线式管理,一部分设备不是总线式管理,管理起来就没有统一化这么方便;
(4)现在的设备很多都有电源管理的需求,也就是休眠和唤醒功能,如果设备都接在总线上,我们可以直接调用总线的休眠/唤醒函数,就将挂载在总线上的所有设备都休眠/唤醒;

3、platform总线的注册

3.1、函数调用关系

kernel_init();
	do_basic_setup();
		driver_init();
			platform_bus_init();
				device_register(&platform_bus);
				bus_register(&platform_bus_type);

(1)kernel_init()函数是内核的1号进程,在内核的启动阶段被kernel_thread()函数调用去创建进程;
(2)device_register(&platform_bus):注册platform设备,将来在会看到/sys/devices/platform目录;
(3)bus_register(&platform_bus_type):向总线系统注册platform总线,将来会看到/sys/bus/platform目录;

3.2、struct bus_type结构体

struct bus_type {
	const char		*name;	
	struct bus_attribute	*bus_attrs;
	struct device_attribute	*dev_attrs;
	struct driver_attribute	*drv_attrs;

	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 (*suspend)(struct device *dev, pm_message_t state);	//总线挂起函数
	int (*resume)(struct device *dev);	//总线唤醒函数

	const struct dev_pm_ops *pm;	//电源管理相关

	struct bus_type_private *p;		//总线私有数据
}; 

(1)name:总线的名字;
(2)match:总线的匹配函数,就是总线上的设备和驱动如何匹配,这是总线最重要的函数;
(3)该结构体是内核中描述一种总线的结构体;

3.3、platform_bus全局变量 & platform_bus_type全局变量

struct device platform_bus = {
	.init_name	= "platform",
};

struct bus_type platform_bus_type = {
	.name		= "platform",	//总线的名字
	.dev_attrs	= platform_dev_attrs,	//总线下设备的属性
	.match		= platform_match,	//总线上设备和驱动的匹配函数
	.uevent		= platform_uevent,
	.pm		= &platform_dev_pm_ops,	//电源管理相关
};	

重要的是name和match,我们在"/sys/bus"目录下看到platform文件夹就是因为在注册总线时这里赋值为"platform";

4、向platform总线注册设备

4.1、struct platform_device结构体

struct platform_device {
	const char	* name;	//设备的名字
	int		id;		//设备的标号:有可能存在好几个名字一样的设备,这时就要靠标号来区分
	struct device	dev;	//内核中用来表示设备的基础结构体
	u32		num_resources;		//设备占用资源的数量
	struct resource	* resource;	//设备占用资源的具体说明

	const struct platform_device_id	*id_entry; //用来保存匹配上的struct platform_driver结构体中的id_tabele中变量

	/* arch specific additions */
	struct pdev_archdata	archdata;
};

4.2、设备的资源描述

struct resource {
	resource_size_t start;	//表示资源的起始值
	resource_size_t end;	//表示资源的最后一个字节的地址, 如果是中断,end和satrt相同
	const char *name;		// 资源的名字,可不写  
	unsigned long flags;	//资源的类型
	struct resource *parent, *sibling, *child;	//资源的关联性
};

flags的类型说明

#define IORESOURCE_MEM		0x00000200    //内存
#define IORESOURCE_IRQ		0x00000400    //中断

4.3、platform总线设备注册函数

int platform_device_register(struct platform_device *pdev)
{
	device_initialize(&pdev->dev);
	return platform_device_add(pdev);
}

4.4、向platform总线注册设备的函数调用

//获取开发板对应的struct machine_desc结构体,将开发板的初始化
//函数赋值给init_machine全局变量
start_kernel()
	struct machine_desc *mdesc;
	mdesc = setup_machine(machine_arch_type);
	init_machine = mdesc->init_machine;


//在customize_machine()函数中调用init_machine函数指针,也就是调用mdesc->init_machine函数
static int __init customize_machine(void)
{
	/* customizes platform devices, or adds new ones */
	if (init_machine)
		init_machine();
	return 0;
}
arch_initcall(customize_machine);

//X210开发板的mdesc->init_machine函数
//目录:arch/arm/mach-s5pv210/mach-x210.c
smdkc110_machine_init
		platform_add_devices(smdkc110_devices, ARRAY_SIZE(smdkc110_devices));

(1)arch_initcall(customize_machine):把customize_machine放到".initcall3,init"段,在内核启动过程中会被调用;
(2)smdkc110_devices:是一个struct platform_device类型的数组,里面记录了内核需要向platform总线注册的所有设备;
(3)效果:在内核启动过程中,已经向platform总线注册了设备;
(2)怎么解析得到开发板对应的struct machine_desc结构体,参见博客:《内核启动过程中机器码的确定》

5、向platform总线注册驱动

5.1、struct platform_driver结构体

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;
};

5.2、platform总线驱动注册函数

int platform_driver_register(struct platform_driver *drv)
{
	drv->driver.bus = &platform_bus_type; //这个很关键,说明这个驱动是注册到platform总线上的
	
	if (drv->probe)
		drv->driver.probe = platform_drv_probe;	//总线的探测函数,这个函数最终调用的是我们注册驱动时的drv->probe函数
	if (drv->remove)
		drv->driver.remove = platform_drv_remove;
	if (drv->shutdown)
		drv->driver.shutdown = platform_drv_shutdown;

	return driver_register(&drv->driver);
}

(1)将我们构建的struct platform_driver结构体调用driver_register()函数注册到内核;
(2)driver_register()函数是内核统一的驱动注册函数,但是在注册前对总线信息、探测函数进行了赋值,说明这个驱动是注册到platform总线上的;

6、驱动和设备的匹配 & 驱动的prob函数调用

6.1、platform总线上设备和驱动的匹配函数

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);

	/* match against the id table first */
	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);
}

(1)函数的功能就是通过名字来匹配设备和驱动,是platform总线的匹配函数,在注册platform总线时提供;
(2)platform_match_id:检查驱动id_table中的名字和设备的名字是否有一样的,因为有的驱动同时支持好几个名字的设备;
(3)strcmp(pdev->name, drv->name):直接比较驱动的名字和设备的名字是否一样;
(4)platform_match()函数被赋值给platform_bus_type.match,在platform_bus_type变量定义时赋值,是platform总线的设备和驱动匹配函数;

6.2、驱动的注册

platform_driver_register();
	driver_register();
		bus_add_driver();
			driver_attach();
				bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);	//将驱动的名字和platform总线上所有的设备名字进行一一比对
					fn(dev, data); 	//fn函数指针就是__driver_attach()函数
						driver_match_device();	//判断驱动的名字和设备的名字是否匹配,不匹配就返回
							drv->bus->match();	//drv->bus->match函数指针就是platform_match()函数
						driver_probe_device();
							really_probe();
								drv->probe();	//调用我们驱动的probe函数

6.3、整体思路整理

(1)platform总线上的设备是在内核启动阶段就已经注册了,所以设备的注册是早于驱动的注册;
(2)驱动在注册时会逐个和platform总线上的设备匹配,调用platform_match()函数进行名字的匹配;
(3)如果驱动和设备的名字能够匹配上,则调用驱动的probe函数;
(4)如果platform总线上有多个设备的名字和驱动程序匹配,则驱动的probe函数会被调用多次,在sysfs中会有后缀来区分
这几个设备;

7、platform设备如何传递数据给platform驱动

//设备要传给驱动的数据
static struct s3c24xx_led_platdata mini2440_led1_pdata = {
	.name		= "led1",
	.gpio		= S3C2410_GPB(5),
	.flags		= S3C24XX_LEDF_ACTLOW | S3C24XX_LEDF_TRISTATE,
	.def_trigger	= "heartbeat",
};

//表示platform总线的设备结构体
static struct platform_device mini2440_led1 = {
	.name		= "s3c24xx_led",
	.id		= 1,
	.dev		= {
		.platform_data	= &mini2440_led1_pdata, //要传给驱动的数据
	},
};

//驱动的prob函数
static int s3c24xx_led_probe(struct platform_device *dev)
{
	······
	struct s3c24xx_led_platdata *pdata = dev->dev.platform_data; //解析出设备传递的数据
	······
}

//表示platform总线的驱动结构体
static struct platform_driver s3c24xx_led_driver = {
	.probe		= s3c24xx_led_probe,
	.remove		= s3c24xx_led_remove,
	.driver		= {
		.name		= "s3c24xx_led",
		.owner		= THIS_MODULE,
	},
};

platform总线设备注册时将要传给驱动的数据一起注册,保存在static struct platform_device.dev.platform_data指针变量中;驱动会在prob函数中将其解析出来;

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

正在起飞的蜗牛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值