https://mp.csdn.net/postedit/80850526
https://mp.csdn.net/postedit/81124536
https://mp.csdn.net/postedit/81145148
前两篇文章以例子的形式举例了platform总线的使用,
第三篇文章结合前两篇文章抽象出公有的部分,以通用性描述总结了platform总线如何使用,以及注意点,这篇文章分析一下platfrom总线的是如何让实现的。
paltform总线对Linux驱动工程师是非常重要的,因为编写的大多数设备驱动,都是借用了plaftom总线来实现。
下面就是platform在驱动层次中的体现。
other driver主要是对platform总线的使用。我在前面的三个链接都有体现。
platform则是我们这次分析的重点。
底层的设备模型我会在后面分析。
说明:
内核启动时,要完成一定的初始化操作之后,才会处理device和driver的注册及probe,因此在这之前,常规的platform设备是无法使用的。但是在Linux中,有些设备需要尽早使用(如在启动过程中充当console输出的serial设备),所以platform模块提供了一种称作Early platform device/driver的机制,允许驱动开发人员,在开发驱动时,向内核注册可在内核早期启动过程中使用的driver。这些机制提供了如下接口:
int __init early_platform_driver_register(struct early_platform_driver *epdrv, char *buf);
void __init early_platform_add_devices(struct platform_device **devs, int num);
void __init early_platform_driver_register_all(char *class_str);
early_platform_match(struct early_platform_driver *epdrv, int id);
static __init int early_platform_left(struct early_platform_driver *epdrv, int id);
static int __init early_platform_driver_probe_id(char *class_str,int id, int nr_probe);
int __init early_platform_driver_probe(char *class_str,int nr_probe, int user_only);
void __init early_platform_cleanup(void);
在我的2.6.35.7的内核中,只有console是用了early_platform总线的。这个因为应用比较少,这里先不分析,等有空了我学习了再补充这个博客。
1.platform_bus的实现
struct device platform_bus = {
.init_name = "platform",
};
/* platform属于总线的一种,所以需要注册他的bus */
struct bus_type platform_bus_type = {
.name = "platform",
.dev_attrs = platform_dev_attrs,
.match = platform_match, /* 整platform总线上,drvice和driver匹配的机制 */
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
int __init platform_bus_init(void)
{
int error;
early_platform_cleanup(); /* 清除早期的platform数据和信息(早期的platform建立时sysfs等都没建立,不完善,所以要这里清掉之前的建立),重新注册platform */
error = device_register(&platform_bus); /* 在sysfs的device下注册一个位platform的空文件夹 */
if (error)
return error;
error = bus_register(&platform_bus_type); /* 在sysfs的bus下注册一个platform的文件夹 ,同时使用platform总线注册的设备的bus都会被赋值为platform_bus_type,即将来所有设备会在bus下的platform里出现*/
if (error)
device_unregister(&platform_bus);
return error;
}
platform总线上设备和驱动的匹配规则
/**
* 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);
/* match against the id table first */
if (pdrv->id_table) /* 如果driver的id_table存在,则只匹配id_table */
return platform_match_id(pdrv->id_table, pdev) != NULL;
/* fall-back to driver name match */
return (strcmp(pdev->name, drv->name) == 0); /* 如果drv的id_table不存在,则比较name是否相等 */
}
id的匹配规则
static const struct platform_device_id *platform_match_id(
const struct platform_device_id *id,
struct platform_device *pdev)
{
/* 其实id也是匹配name,只不过可能driver要支持多个device,所以有多个名字,就做成一个id_table比较 */
while (id->name[0]) {
if (strcmp(pdev->name, id->name) == 0) {
pdev->id_entry = id;
return id;
}
id++;
}
return NULL;
}
2.platform_device_register的实现
platform_device主要是基于下面这个结构体实现
struct platform_device {
const char * name; /* 匹配driver使用,重要 */
int id; /* 区分使用相同driver的同种设备,从0开始,-1为自动分配 */
struct device dev; /* 具体的设备,无论是何种总线里的设备,都是对dev再次增加信息封装 */
u32 num_resources; /* 多少个资源,为下面准备的 */
struct resource * resource; /* 存放资源的地址 */
const struct platform_device_id *id_entry; /* 区分统一厂家的不同版本设备 */
/* arch specific additions */
struct pdev_archdata archdata; /* 为特殊架构准备,基本不用 */
};
通常我们自定义一个静态的platform_device并填充。
当然内核为了绝对的可裁剪性,也提供了通过动态分配的方法。
struct platform_object {
struct platform_device pdev; /* obj包含有platform_device */
char name[1];
};
/**
* platform_device_alloc - create a platform device
* @name: base name of the device we're adding
* @id: instance id
*
* Create a platform device object which can have other objects attached
* to it, and which will have attached objects freed when it is released.
*/
struct platform_device *platform_device_alloc(const char *name, int id)
{
struct platform_object *pa;
pa = kzalloc(sizeof(struct platform_object) + strlen(name), GFP_KERNEL);
if (pa) {
strcpy(pa->name, name);
pa->pdev.name = pa->name;
pa->pdev.id = id;
device_initialize(&pa->pdev.dev);
pa->pdev.dev.release = platform_device_release; /* release放在dev里面unregister的时候只要device_del(dev)就可以 */
}
return pa ? &pa->pdev : NULL;
}
/**
* platform_device_add_resources - add resources to a platform device
* @pdev: platform device allocated by platform_device_alloc to add resources to
* @res: set of resources that needs to be allocated for the device
* @num: number of resources
*
* Add a copy of the resources to the platform device. The memory
* associated with the resources will be freed when the platform device is
* released.
*/
int platform_device_add_resources(struct platform_device *pdev,
const struct resource *res, unsigned int num)
{
struct resource *r;
r = kmalloc(sizeof(struct resource) * num, GFP_KERNEL);
if (r) {
memcpy(r, res, sizeof(struct resource) * num);
pdev->resource = r;
pdev->num_resources = num;
}
return r ? 0 : -ENOMEM;
}
static void platform_device_release(struct device *dev)
{
struct platform_object *pa = container_of(dev, struct platform_object,
pdev.dev);
kfree(pa->pdev.dev.platform_data); /* 这部分由自己具体的设备实现具体的数据结构 */
kfree(pa->pdev.resource); /* 释放存放resource的内存 */
kfree(pa); /* 释放platform_device_alloc中申请的obj */
}
无论是静态还是动态定义的platform_device,都需要platform_device添加进platform_bus。
即使用下面这个函数就可以了。
/**
* platform_device_register - add a platform-level device
* @pdev: platform device we're adding
*/
int platform_device_register(struct platform_device *pdev)
{
device_initialize(&pdev->dev); /* 初始化device结构体里面的通用数据 */
return platform_device_add(pdev); /* 真正添加device到platform_bus */
}
系统也提供了可以注册多个device的接口,起始就是多次调用上面的。
/**
* platform_add_devices - add a numbers of platform devices
* @devs: array of platform devices to add
* @num: number of platform devices in array
*/
int platform_add_devices(struct platform_device **devs, int num)
{
int i, ret = 0;
for (i = 0; i < num; i++) {
ret = platform_device_register(devs[i]);
if (ret) {
while (--i >= 0)
platform_device_unregister(devs[i]);
break;
}
}
return ret;
}
下面这个函数就是注册platform总线的核心。
/**
* platform_device_add - add a platform device to device hierarchy
* @pdev: platform device we're adding
*
* This is part 2 of platform_device_register(), though may be called
* separately _iff_ pdev was allocated by platform_device_alloc().
*/
int platform_device_add(struct platform_device *pdev)
{
int i, ret = 0;
if (!pdev)
return -EINVAL;
if (!pdev->dev.parent)
pdev->dev.parent = &platform_bus; /* 如果设备没指定parent的话,默认就是platform(通常都是) */
pdev->dev.bus = &platform_bus_type; /* 绑定总线类型,sysfs体现 */
if (pdev->id != -1) /* 指定了具体id */
dev_set_name(&pdev->dev, "%s.%d", pdev->name, pdev->id);
else /* -1,自动分配id */
dev_set_name(&pdev->dev, "%s", pdev->name);
/* 对资源进行处理和检查 */
for (i = 0; i < pdev->num_resources; i++) {
struct resource *p, *r = &pdev->resource[i];
if (r->name == NULL) /* 该resouce没name的话,则使用该dev的name */
r->name = dev_name(&pdev->dev);
p = r->parent;
if (!p) {
if (resource_type(r) == IORESOURCE_MEM)
p = &iomem_resource; /* 没指定resouce的父项,则用系统默认的 PCI MEM */
else if (resource_type(r) == IORESOURCE_IO)
p = &ioport_resource; /* 没指定resouce的父项,则用系统默认的 PCI IO */
}
/* 把该soucurce插入到source tree中 */
if (p && insert_resource(p, r)) {
printk(KERN_ERR
"%s: failed to claim resource %d\n",
dev_name(&pdev->dev), i);
ret = -EBUSY;
goto failed;
}
}
/* 调试信息 */
pr_debug("Registering platform device '%s'. Parent at %s\n",
dev_name(&pdev->dev), dev_name(pdev->dev.parent));
/* 把该设备增加到设备节点中去,对应的在sysfs的bus,platform下和sysfs的device,platform会出现响应设备 */
ret = device_add(&pdev->dev);
if (ret == 0)
return ret;
failed:
while (--i >= 0) {
struct resource *r = &pdev->resource[i];
unsigned long type = resource_type(r);
if (type == IORESOURCE_MEM || type == IORESOURCE_IO)
release_resource(r);
}
return ret;
}
删除和注册刚好相反,就不分析了
/**
* platform_device_del - remove a platform-level device
* @pdev: platform device we're removing
*
* Note that this function will also release all memory- and port-based
* resources owned by the device (@dev->resource). This function must
* _only_ be externally called in error cases. All other usage is a bug.
*/
void platform_device_del(struct platform_device *pdev)
{
int i;
if (pdev) {
device_del(&pdev->dev);
for (i = 0; i < pdev->num_resources; i++) {
struct resource *r = &pdev->resource[i];
unsigned long type = resource_type(r);
if (type == IORESOURCE_MEM || type == IORESOURCE_IO)
release_resource(r);
}
}
}
2.driver部分
struct platform_driver {
int (*probe)(struct platform_device *); /* 负责使用device传递过来的platform_data初始化具体的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; /* 驱动程序共有接口,也是实现在sysfs接口的核心 */
const struct platform_device_id *id_table;
};
驱动的注册很简单,主要是调用系统提供的driver_regiser接口。
/**
* platform_driver_register - register a driver for platform-level devices
* @drv: platform driver structure
*/
int platform_driver_register(struct platform_driver *drv)
{
drv->driver.bus = &platform_bus_type; /* 驱动的总线类型也是platfrom */
if (drv->probe)
drv->driver.probe = platform_drv_probe; /* 在驱动层没实现probe函数的情况下,则掉用系统默认的probe函数 */
if (drv->remove)
drv->driver.remove = platform_drv_remove;
if (drv->shutdown)
drv->driver.shutdown = platform_drv_shutdown;
return driver_register(&drv->driver);
}
driver的卸载就更简单了
/**
* platform_driver_unregister - unregister a driver for platform-level devices
* @drv: platform driver structure
*/
void platform_driver_unregister(struct platform_driver *drv)
{
driver_unregister(&drv->driver);
}
我们发现,其实platform总线的device和driver本身注册并没有做任何实质性的东西,真正实质性的注册为driver_register和device_add函数。这两个也是各种总线来实现驱动和设备注册的根本函数。我在下一个博客继续学习分析。