分类: LINUX
2011-03-25 23:30:53
|
内核:2.6.29
作者: hjlin
现实的linux设备和驱动都是挂接在某一个具体的总线上。比如,pci、usb、i
Platform的bus_type实例。主要提供了match,uevent和电源管理相关的方法。
struct bus_type platform_bus_type = {
.name = "platform",
.dev_attrs = platform_dev_attrs,
.match = platform_match,
.uevent = platform_uevent,
.suspend = platform_suspend,
.suspend_late = platform_suspend_late,
.resume_early = platform_resume_early,
.resume = platform_resume,
};
注意这句话.dev_attrs = platform_dev_attrs
static struct device_attribute platform_dev_attrs[] = {
__ATTR_RO(modalias),
__ATTR_NULL,
};
#define __ATTR_RO(_name) { \
.attr = { .name = __stringify(_name), .mode = 0444 }, \
.show = _name##_show, \
}
可知platform下的所有设备都有一个属性(就是sysfs中的一个文件),名字叫modalias,读取方法为 modalias_show 的方法。在 读方法中返回的就是platform:device_name这样的字符串。
platform总线驱动和设备匹配的规则就是他们的名字字符串是否相同。
static int platform_match(struct device * dev, struct device_driver * drv)
{
struct platform_device *pdev = container_of(dev, struct platform_device, dev);
return (strncmp(pdev->name, drv->name, BUS_ID_SIZE) == 0);
}
在kobject_uevent时候添加一个key=MODALIAS,value=platform:device_name的值.(但是在kobject_uevent的实现里,暂时没有找到如何调用到这个方法的?)
static int platform_uevent(struct device *dev, struct kobj_uevent_env *env)
{
struct platform_device *pdev = to_platform_device(dev);
add_uevent_var(env, "MODALIAS=%s%s", PLATFORM_MODULE_PREFIX,
(pdev->id_entry) ? pdev->id_entry->name : pdev->name);
return 0;
}
内核在初始化platform框架的时候,通过bus_register注册了定义的platform_bus_type
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);
return error;
}
Bus_register已经分析过,可知经过bus_register后的sys文件系统结构如下:
/sys/bus/platform/
|-- devices
| |-- …
|-- drivers
| |-- …
|-- drivers_autoprobe
|-- drivers_probe
`-- uevent
Platform设备简单的封装了struct device,主要是增加了struct resource来表示设备在系统中所占用的内存或者中断资源等。
struct platform_device {
const char * name; //设备名称,和设备id一起设置内嵌kobj的name
int id; //设备id,和设备名称一起设置内嵌kobj的name
struct device dev; //内部封装的device
u32 num_resources;
struct resource * resource; //一些io或者中断资源
};
内核提供了注册platform_device的方法。相对于默认的device_register,platform_device_register方法主要多做了以下的工作:
1. 设置父kobj为platform_bus的内嵌kobj,platform_bus是一个struct device
struct device platform_bus = {
.init_name = "platform",
};
并且在__init platform_bus_init(void)方法中,通过device_register(&platform_bus);注册到系统中,并在sys中创建/sys/platform表示它。因此platform_device_register注册后的device的sys目录应该类似/sys/platform/xxx_device的结构。
?为什么要这样做,我觉得主要是让sys层次更加清晰易懂。
2. 设置device的bus为platform_bus_type,理所当然的了。不知道具体哪个总线根本没法匹配驱动。每个真实的驱动必须都有一个具体的总线类型如果你使用驱动模型的话。
3. 通过insert_resource将设备定义的所要分配的资源注册到内核。
经过platform_device_register注册的设备,在sys中看起来的结构可能是:
/sys/devices/platform/serial8250.0
|-- uevent
|-- modalias
|-- subsystem -> ../../../bus/platform
|-- power
|-- tty
|-- driver -> ../../../bus/platform/drivers/serial8250
/sys/bus/platform/devices
|-- serial8250.0 -> ../../../devices/platform/serial8250.0
int platform_device_register(struct platform_device * pdev)
{
device_initialize(&pdev->dev);
return platform_device_add(pdev);
}
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;
pdev->dev.bus = &platform_bus_type;
if (pdev->id != -1)
snprintf(pdev->dev.bus_id, BUS_ID_SIZE, "%s.%d", pdev->name,
pdev->id);
else
strlcpy(pdev->dev.bus_id, pdev->name, BUS_ID_SIZE);
//分配资源
for (i = 0; i < pdev->num_resources; i++) {
struct resource *p, *r = &pdev->resource[i];
if (r->name == NULL)
r->name = pdev->dev.bus_id;
p = r->parent;
if (!p) {
if (r->flags & IORESOURCE_MEM)
p = &iomem_resource;
else if (r->flags & IORESOURCE_IO)
p = &ioport_resource;
}
if (p && insert_resource(p, r)) {
printk(KERN_ERR
"%s: failed to claim resource %d\n",
pdev->dev.bus_id, i);
ret = -EBUSY;
goto failed;
}
}
pr_debug("Registering platform device '%s'. Parent at %s\n",
pdev->dev.bus_id, pdev->dev.parent->bus_id);
ret = device_add(&pdev->dev);
if (ret == 0)
return ret;
failed:
while (--i >= 0)
if (pdev->resource[i].flags & (IORESOURCE_MEM|IORESOURCE_IO))
release_resource(&pdev->resource[i]);
return ret;
}
相对于标准的struct device_drvier,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 (*suspend_late)(struct platform_device *, pm_message_t state);
int (*resume_early)(struct platform_device *);
int (*resume)(struct platform_device *);
struct device_driver driver; //内部封装的device_driver
};
类似platform_driver,就是对driver_register的一层很简单的包装。在sys文件系统中的结构可能是:
/sys/bus/platform/drivers/serial8250
|-- serial8250 -> ../../../../devices/platform/serial8250
|-- serial8250.0 -> ../../../../devices/platform/serial8250.0
|-- uevent
|-- unbind
|-- bind
int platform_driver_register(struct platform_driver *drv)
{
drv->driver.bus = &platform_bus_type;
if (drv->probe)
drv->driver.probe = platform_drv_probe;
if (drv->remove)
drv->driver.remove = platform_drv_remove;
if (drv->shutdown)
drv->driver.shutdown = platform_drv_shutdown;
if (drv->suspend)
drv->driver.suspend = platform_drv_suspend;
if (drv->resume)
drv->driver.resume = platform_drv_resume;
return driver_register(&drv->driver);
}
首先在bsp中,需要注册所有板子上的platform设备,比如lcd,rtc等(就是cpu集成的这些设备芯片),否则将没有机会识别该设备。pci或者usb上面的设备内核可以检测识别。集成的一定要手动配置的。这应该就是bsp的意思了吧。
举个例子, arch/arm/mach-s
static struct platform_device *smdk2410_devices[] __initdata = {
&s
&s
&s
&s
&s
};
大概说这个板子的内建芯片设备有这么多种。
static void __init smdk2410_init(void)
{
platform_add_devices(smdk2410_devices, ARRAY_SIZE(smdk2410_devices));
smdk_machine_init();
}
然后在init中会将这些platform设备添加到设备模型中。这样后来加载驱动的时候就会匹配到这些设备了。从而调用到驱动的probe函数。Probe函数呢,就是驱动的核心了,一般主要有两种事情:
A. 申请中断,注册中断服务程序,io端口等一些和硬件相关的事情。
B. 注册字符驱动等可以和用户态进行交互的事情。
C. 第3个是我们看不着的事情,因为内核的设备驱动模型已经帮我们默默的做了很多事情了。比如在/sys文件系统层次展示,引用计数实现资源管理,uevent的热插拔。甚至是更具体的事情,比如说input子系统,她把上面同用户态交互的B的事情都替你干好了。实在是很贤惠啊。我们唯一需要做的就是按照内核的驱动模型来编写代码就可以了。