linux驱动(第四课 驱动架构,platform)

在BDD模型下,总线设备驱动分离。
总线负责匹配设备和驱动,而驱动以标准途径拿到板级硬件资源。
软件分层是linux驱动的基本思想。
驱动被分离为核心层,硬件层等。
驱动核心层用来处理某一类驱动的公共处理部分,从而让硬件层只需要专注与硬件操作。

对于PCI,USB,IIC,SPI等设备而言,他们本身挂载在一个具体的BUS上,自然不成问题。但是对于SOC,有些外设却是挂载在SOC的内部总线上。为此,linux设置了一种虚拟的总线,称为PlatformBus。相应的设备,被称为platform_device,而驱动,称为platform_driver。

PlatformBus为设备提供了一种架构清晰的注册手段。例如一个设备,它是一个CDEV,但是同时,它也被实现为一个platform_device。这样,用户就可以通过两种机制访问到这个设备。既可以通过/dev,以CDEV的方式来请求CDEV驱动进行IO,也可以通过/sys/bus/platform,以platform_device的方式来请求platform_driver驱动进行IO。
但是设备在注册时,是由platform_driver_probe来发起对CDEV的注册。
在通常的OFSTYLE的加载过程中,probe函数被调用时,需要在probe中进行CDEV的注册。
而不再是传统的CDEV在module_init函数中去注册了。

来看看具体实现。

struct platform_device{
	const char* name;
	int id;

	struct device dev;
	struct resource* resource;
	u32 num_resource;

	const struct platform_device_id* id_entry;

}

和BDD架构中的所有结构体一样,它有一个name成员,用于字符串比较。
可以看出,platform_device内嵌了一个DEV的实体,是对DEV的扩展。
它关联到一个RESOURCE结构体的VectorTable,并指定了长度。
它关联到一个platform_device_id结构体对象,这是一个描述符,用来对PDEV进行描述。

struct platform_device_id {
	char name[PLATFORM_NAME_SIZE];
	ulong driver_data;
};

这是一个PDEVID的结构体定义。
它有一个name成员,以及一个driver_data成员。

来看看RESOURCE是怎么实现的。

struct resource{
	const char* name;
	
	size_t start;
	size_t end;
	unsigned long flags;
	
	struct resource * parent;
	struct resource * child;
	struct resource * sibling;
	
};

可以看出,由于插入了指针,所以RESOURCE构成树状结构,可以索引到父节点(父元),也可以索引到子节点(子首元)。也可以索引到弟节点(后元)。
RESOURCE具有一个name成员,这可以用来进行字符串比较。
它的关键成员是,start,end,flags。
这将对应到DTB中的属性。
flags可以为
1)IORESOURCE_IO,此时start,end代表IONUM的起始号和结束号。
如果只使用了一个IO,那么start==end。

2)IORESOURCE_MEM,此时start,end代表MEM的起始地址和结束地址。

3)IORESOURCE_IRQ,此时start,end代表IRQNUM的起始号和结束号。
如果只用了1个IRQNUM,那么start==end。
4)IORESOURCE_DMA,此时start,end代表DMANUM的起始号和结束号。
5)IORESOURCE_BUS。
5)IORESOURCE_REG。

struct resource* platform_get_resource(struct platform_device* dev, uint type, uint num);

是用来获取RESOURCE的内核API。它通过传入的PDEV,获取RESOURCE的句柄。

另一种RESOURCE,就是platform_data。(PLTDATA)
我们可以在注册时,同时注册私有数据。它是非标的,可以注册到device里。

static struct my_pltdata {
	int reset_gpio;
	int led_gpio;
};
static struct my_pltdata needed_pltdata = {
	.reset_gpio = 47,
	.led_gpio = 41,
};

static struct resource my_resources[] = {
	[0] = {
		.name = "mem1",
		.start = BASEADDR1,
		.end = (BASEADDR1+ SIZE - 1),
		.flags = IORESOURCE_MEM,
	},
	[1] = {
		.name = "mem2",
		.start = BASEADDR2,
		.end = (BASEADDR2+ SIZE - 1),
		.flags = IORESOURCE_MEM,
	},
	[2] = {
		.name = "mc",
		.start = IRQ1,
		.end = IRQ1,
		.flags = IORESOURCE_IRQ,
	},

};
static struct platform_device mydev = {
	.name = "my pltdev"
	.id = 0,
	.dev = {
		.platform_data = &needed_pltdata ,
	},
	.resource = my_resources,
	.num_resource = ARRY_SIZE(my_resources),
};
platform_device_register(&mydev );

这个例子中,我们为PDEV配置了RESOURCE,同时也为PDEV配置了PLTDATA,放置在PDEV.dev.platform_data中。
这里用到了一个宏ARRY_SIZE,用来帮助我们得到一个TABLE的实际长度。

struct platform_driver{
	struct device_driver driver;
	const struct platform_device_id* id_table;	


	int (*probe)(struct platform_device*);
	int (*remove)(struct platform_device*);
}

可以看出,platform_driver内嵌了一个device_driver实体,是对DRV的扩展。
它关联到一个platform_device_id结构体对象,这是一个描述符,用来对PDEV进行描述。

系统为PlatformBus定义了一个BUS的实例,

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

这是一个全局的变量,内核启动时,已经注册了PlatformBus。
内核也实现了match函数。

static int platform_match(struct device *dev, struct device_driver *drv)
{
	int ret;
	struct platform_device *pdev = to_platform_device(dev);
	struct platform_driver *pdrv = to_platform_driver(drv);
	
	ret = of_driver_match_device(dev, drv);
	if(ret)
		return 1;

	if(pdrv->id_table){
		ret = platform_match_id(pdrv->id_table, pdev)
		return ((ret != NULL));
	}
	
	ret = strcmp(pdev->name, drv->name);
	return ((ret == 0));
}

首先是进行OFSTYLE的匹配,如果不成功就换下一种方式匹配,即PDEVID表匹配。
PDEVID表匹配时,查看PDEV是否出现在platform_driver的PDEVID表内。如果不成功,就换下一种方式匹配,即名称匹配。比较PDEV的名字和PDRV的名字是否一致。

来看一个简单的例子。定义了两个模块,PDRV和PDEV。

struct platform_driver pdrv = {
	.driver = {
		.name = "pdrv",
		.owner = THIS_MODULE,
		.pm = &pdrv_pm_ops,
	},
	.probe = pdrv_probe,
	.remove = pdrv_remove,
	
};

static const struct dev_pm_ops pdrv_pm_ops = {
	.suspend = pdrv_suspend,
	.resume = pdrv_resume,
};

static int pdrv_suspend(struct device * dev)
{
	return 0;
}

static int pdrv_resume(struct device * dev)
{
	return 0;
}

static int pdrv_probe(struct platform_device * pdev)
{
	return 0;
}

static int pdrv_remove(struct platform_device * pdev)
{
	return 0;
}

module_platform_driver(pdrv);

这个模块中,定义了PDRV,并利用系统提供的宏module_platform_driver,将PDRV注册到了内核中。

struct platform_device pdev0 = {
	.name= "pdrv",
	.id = 0,
	.num_resource  = 0,
	.resource = NULL,
	.dev = {
		.release = pdev_release,
	}


}

struct platform_device pdev1 = {
	.name= "pdrv",
	.id = 1,
	.num_resource  = 0,
	.resource = NULL,
	.dev = {
		.release = pdev_release,
	}
}
static void pdev_release(struct device * dev)
{
	return;
}
static int __init pdev_init(void)
{
	platform_device_register(&pdev0);
	platform_device_register(&pdev1);
	return 0;
}
module_init(pdev_init);

这个模块中,定义了两个PDEV,他们具有不同的ID,所以PLATBUS可以区分这两个设备。但是name是一样的,都是和所需要匹配的DRV的名字相同。

使用DTB进行板级配置,是推荐的方式。
在DTB中配置DEVICE的RESOURCE,COMPATIBLE。
在驱动模块中定义全局变量OF_DEVICE_ID_MATCH_TABLE。
在DRIVER中配置OF_MATCH_TABLE。
当DTB中的OF_DEVICE_ID能够匹配DRVIER的OF_MATCH_TABLE中给出的OF_DEVICE_ID时,就将DEVICE和DRIVER绑定起来。
这样就实现了驱动和设备分离。

在linux中,BUS是最重要的部分。
LDM中,BUS维护着DEV列表,BUS也通过内核机制,间接维护着DRV集合(DRVSET)。
可以简单理解为,BUS维护着从属于BUS的DEV和DRV,以及DEV和DRV之间的绑定关系。

mdev负责管理着BDD。尤其是对于HotPlug的设备。
当内核通过uevent通知mdev进行新设备管理时,BUS会执行match,如果match成功,mdev会请求内核加载DRIVER。在内核加载DRIVER时,内核会调用DRIVER的probe函数。
这是一个通用的过程,对于任何BUS都一样。例如PlatformBus,IIC,SPI,USB,PCI等等。

整个BDD是一个树状结构,如果是SOC中的USBController,而这个USBC是由内部总线进行访问的,那么USBC所管理的BUS,也是PlatformBus的一个子节点。这是容易从逻辑上理解的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值