深入浅出linux之总线和设备(platform总线)
来源:互联网 发布:c语言关键字有哪些 编辑:程序博客网 时间:2024/06/08 15:18
在设备基本模型一节,我们了解到总线发现设备,管理设备,配置设备的功能。一般来说,总线都是物理存在的,但是linux系统提供了一种简单的总线platform。Platform并不是一种物理存在的总线,而是个逻辑概念。PC机系统提供了一条根总线(PCI总线)来管理设备,但是有些设备,并不由PCI总线管理,于是linux内核虚拟了一条platform总线,来统一管理这些设备。
Platform总线虽然是最简单的总线,但是一样具有总线的通用功能。通过分析这种简单的总线,可以帮助我们理解总线的概念和总线对设备和驱动的管理,方便后面对复杂总线的分析,比如说PCI总线。
我们选的例子是q40kbd,在目录drivers/input/serio里面。实际上,这就是一个键盘控制器驱动(不是键盘驱动)。而它使用了platform总线。
一 从驱动发现设备的过程
===========================q40kbd_init==================================
static int __init q40kbd_init(void)
{
int error;
if (!MACH_IS_Q40)
return -EIO;
/*驱动作为platform总线驱动注册*/
error = platform_driver_register(&q40kbd_driver);
if (error)
return error;
/*分配一个platform设备*/
q40kbd_device = platform_device_alloc("q40kbd", -1);
if (!q40kbd_device)
goto err_unregister_driver;
/*platform设备注册*/
error = platform_device_add(q40kbd_device);
if (error)
goto err_free_device;
return 0;
err_free_device:
platform_device_put(q40kbd_device);
err_unregister_driver:
platform_driver_unregister(&q40kbd_driver);
return error;
}
这段代码很简单,就是先注册platform驱动,再注册platform设备。先分析驱动的注册。
================================platform_driver_register====================
int platform_driver_register(struct platform_driver *drv)
{
/*驱动的总线赋值为platform总线*/
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);
}
============================driver_register===============================
int driver_register(struct device_driver * drv)
{
if ((drv->bus->probe && drv->probe) ||
(drv->bus->remove && drv->remove) ||
(drv->bus->shutdown && drv->shutdown)) {
printk(KERN_WARNING "Driver '%s' needs updating - please use bus_type methods\n", drv->name);
}
klist_init(&drv->klist_devices, klist_devices_get, klist_devices_put);
init_completion(&drv->unloaded);
return bus_add_driver(drv);
}
这个函数做参数检查和初始化后,实际调用了bus_add_driver
==============================bus_add_driver=============================
int bus_add_driver(struct device_driver * drv)
{
struct bus_type * bus = get_bus(drv->bus);
int error = 0;
if (bus) {
pr_debug("bus %s: add driver %s\n", bus->name, drv->name);
/*这段代码使用了kobject,实际是在sysfs里面创建文件*/
error = kobject_set_name(&drv->kobj, "%s", drv->name);
if (error) {
put_bus(bus);
return error;
}
drv->kobj.kset = &bus->drivers;
if ((error = kobject_register(&drv->kobj))) {
put_bus(bus);
return error;
}
/**/
driver_attach(drv);
/*驱动加入总线的驱动列表*/
klist_add_tail(&drv->knode_bus, &bus->klist_drivers);
/*这行和下面一行也是为了在sysfs创建驱动的属性文件和模块*/
module_add_driver(drv->owner, drv);
driver_add_attrs(bus, drv);
add_bind_files(drv);
}
return error;
}
这里有kobject_register函数调用和driver_add_attrs。从前文的sysfs文件系统和koject分析,这些函数都是为了在根目录的sys目录下面创建目录和文件,每个设备都有自己的一些特性文件,就不一一分析了。
driver_attach真正执行了驱动的加载。继续看这个函数。
======================================================================
void driver_attach(struct device_driver * drv)
{
bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}
=======================bus_for_each_dev==================================
int bus_for_each_dev(struct bus_type * bus, struct device * start,
void * data, int (*fn)(struct device *, void *))
{
struct klist_iter i;
struct device * dev;
int error = 0;
if (!bus)
return -EINVAL;
klist_iter_init_node(&bus->klist_devices, &i,
(start ? &start->knode_bus : NULL));
while ((dev = next_device(&i)) && !error)
error = fn(dev, data);
klist_iter_exit(&i);
return error;
}
这段代码就是遍历总线上已经挂载的设备,然后调用fn函数。Fn就是前面传入的函数指针__driver_attach。所以继续分析__driver_attach
================================================================
static int __driver_attach(struct device * dev, void * data)
{
struct device_driver * drv = data;
/*
* Lock device and try to bind to it. We drop the error
* here and always return 0, because we need to keep trying
* to bind to devices and some drivers will return an error
* simply if it didn't support the device.
*
* driver_probe_device() will spit a warning if there
* is an error.
*/
if (dev->parent) /* Needed for USB */
down(&dev->parent->sem);
down(&dev->sem);
if (!dev->driver)
driver_probe_device(drv, dev);
up(&dev->sem);
if (dev->parent)
up(&dev->parent->sem);
return 0;
}
这段代码主要是执行driver_probe_device。
======================================================================
int driver_probe_device(struct device_driver * drv, struct device * dev)
{
int ret = 0;
/*先调用总线配置的match函数*/
if (drv->bus->match && !drv->bus->match(dev, drv))
goto Done;
pr_debug("%s: Matched Device %s with Driver %s\n",
drv->bus->name, dev->bus_id, drv->name);
dev->driver = drv;
/*bus的match函数通过后,继续调用bus的probe函数*/
if (dev->bus->probe) {
ret = dev->bus->probe(dev);
if (ret) {
dev->driver = NULL;
goto ProbeFailed;
}
} else if (drv->probe) {
/*如果驱动提供了probe函数,则调用驱动的probe函数*/
ret = drv->probe(dev);
if (ret) {
dev->driver = NULL;
goto ProbeFailed;
}
}
/*设备发现了驱动,通过sysfs创建一些文件。和设备做符号链接*/
device_bind_driver(dev);
ret = 1;
pr_debug("%s: Bound Device %s to Driver %s\n",
drv->bus->name, dev->bus_id, drv->name);
goto Done;
ProbeFailed:
if (ret == -ENODEV || ret == -ENXIO) {
/* Driver matched, but didn't support device
* or device not found.
* Not an error; keep going.
*/
ret = 0;
} else {
/* driver matched but the probe failed */
printk(KERN_WARNING
"%s: probe of %s failed with error %d\n",
drv->name, dev->bus_id, ret);
}
Done:
return ret;
}
简要概述这段代码,就是先match,然后probe。如果驱动有自己的probe函数,还要调用驱动的probe。从前面的分析知道,match就是platform_match,而platform没有提供probe函数,调用的probe就是platform_drv_probe。这样继续分析platform_match和platform_drv_probe函数。
======================================================================
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);
}
很简单,就是比较驱动的名字和设备的名字是否相同,相同局可以匹配。而注册的Q40kbd驱动的名字是“q40kbd”,也就是说要找到这个名字的设备,两者就匹配了。
======================================================================
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);
return drv->probe(dev);
}
这里又调用了驱动本身的probe函数。这个就是q40kbd_probe。
======================================================================
static int __devinit q40kbd_probe(struct platform_device *dev)
{
q40kbd_port = kzalloc(sizeof(struct serio), GFP_KERNEL);
if (!q40kbd_port)
return -ENOMEM;
q40kbd_port->id.type = SERIO_8042;
q40kbd_port->open = q40kbd_open;
q40kbd_port->close = q40kbd_close;
q40kbd_port->dev.parent = &dev->dev;
strlcpy(q40kbd_port->name, "Q40 Kbd Port", sizeof(q40kbd_port->name));
strlcpy(q40kbd_port->phys, "Q40", sizeof(q40kbd_port->phys));
serio_register_port(q40kbd_port);
printk(KERN_INFO "serio: Q40 kbd registered\n");
return 0;
}
代码很简单,引出的问题可不小。关键是这一步serio_register_port是做什么?在platform总线里面又多出来个serio,这又是为了什么?这个问题放到下一节分析。
二 从设备找到驱动的过程
从platform驱动的注册过程,我们发现和input的过程很相像,都是一个个遍历设备,看是否和某个驱动匹配。那么下面的加载设备的过程,应该也是遍历驱动,看是否匹配。继续分析是否如此。
======================================================================
int platform_device_add(struct platform_device *pdev)
{
int i, ret = 0;
if (!pdev)
return -EINVAL;
/*如果没父设备,就设置platform_bus为父设备*/
if (!pdev->dev.parent)
pdev->dev.parent = &platform_bus;
/*设置设备的bus为platform_bus_type */
pdev->dev.bus = &platform_bus_type;
if (pdev->id != -1)
snprintf(pdev->dev.bus_id, BUS_ID_SIZE, "%s.%u", pdev->name, pdev->id);
else
strlcpy(pdev->dev.bus_id, pdev->name, BUS_ID_SIZE);
/*把设备io端口和io内存注册到系统*/
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;
}
注意io端口(设备控制寄存器)和io内存注册到系统的那部分代码。回顾设备基本概念一节,PCI总线是扫描设备的配置文件,确定设备的io端口和io内存,而platform不是物理上存在的,那么自然不能自动扫描设备,那么如何配置设备的io端口和io内存哪?
从内核驱动中,可以发现platform设备很多使用了探测的方式。一般是先往一个io端口写数据,看回应得到设备的io端口。至于我们分析的q40kbd设备,它甚至没有注册自己的io端口和内存,而是直接使用了缺省值。说明这是一种很古老的设备了。
继续分析device_add:
======================================================================
int device_add(struct device *dev)
{
struct device *parent = NULL;
char *class_name = NULL;
int error = -EINVAL;
dev = get_device(dev);
if (!dev || !strlen(dev->bus_id))
goto Error;
parent = get_device(dev->parent);
pr_debug("DEV: registering device: ID = '%s'\n", dev->bus_id);
/* first, register with generic layer. */
kobject_set_name(&dev->kobj, "%s", dev->bus_id);
if (parent)
dev->kobj.parent = &parent->kobj;
/*在sys目录生成设备目录*/
if ((error = kobject_add(&dev->kobj)))
goto Error;
/**/
dev->uevent_attr.attr.name = "uevent";
dev->uevent_attr.attr.mode = S_IWUSR;
if (dev->driver)
dev->uevent_attr.attr.owner = dev->driver->owner;
dev->uevent_attr.store = store_uevent;
device_create_file(dev, &dev->uevent_attr);
/*设备的属性文件*/
if (MAJOR(dev->devt)) {
struct device_attribute *attr;
attr = kzalloc(sizeof(*attr), GFP_KERNEL);
if (!attr) {
error = -ENOMEM;
goto PMError;
}
attr->attr.name = "dev";
attr->attr.mode = S_IRUGO;
if (dev->driver)
attr->attr.owner = dev->driver->owner;
attr->show = show_dev;
error = device_create_file(dev, attr);
if (error) {
kfree(attr);
goto attrError;
}
dev->devt_attr = attr;
}
/*创建设备类的符号链接*
if (dev->class) {
sysfs_create_link(&dev->kobj, &dev->class->subsys.kset.kobj,
"subsystem");
sysfs_create_link(&dev->class->subsys.kset.kobj, &dev->kobj,
dev->bus_id);
sysfs_create_link(&dev->kobj, &dev->parent->kobj, "device");
class_name = make_class_name(dev->class->name, &dev->kobj);
sysfs_create_link(&dev->parent->kobj, &dev->kobj, class_name);
}
/*设备的能源管理*/
if ((error = device_pm_add(dev)))
goto PMError;
if ((error = bus_add_device(dev)))
goto BusError;
kobject_uevent(&dev->kobj, KOBJ_ADD);
bus_attach_device(dev);
if (parent)
klist_add_tail(&dev->knode_parent, &parent->klist_children);
if (dev->class) {
/* tie the class to the device */
down(&dev->class->sem);
list_add_tail(&dev->node, &dev->class->devices);
up(&dev->class->sem);
}
/* notify platform of device entry */
if (platform_notify)
platform_notify(dev);
Done:
kfree(class_name);
put_device(dev);
return error;
BusError:
device_pm_remove(dev);
PMError:
if (dev->devt_attr) {
device_remove_file(dev, dev->devt_attr);
kfree(dev->devt_attr);
}
attrError:
kobject_uevent(&dev->kobj, KOBJ_REMOVE);
kobject_del(&dev->kobj);
Error:
if (parent)
put_device(parent);
goto Done;
}
在device_add里面,要为设备在sys目录创建很多目录和文件。还要为别的相关子系统创建链接。这些内容已经多次出现了,这里就不一一分析了。
kobject_uevent是个比较复杂的函数,这里面出现了uevent的概念,这个和linux的hotplug有极大关系。
真正的处理函数是bus_attach_device,继续分析:
======================================================================
void bus_attach_device(struct device * dev)
{
struct bus_type * bus = dev->bus;
if (bus) {
device_attach(dev);
klist_add_tail(&dev->knode_bus, &bus->klist_devices);
}
}
int device_attach(struct device * dev)
{
int ret = 0;
down(&dev->sem);
if (dev->driver) {
device_bind_driver(dev);
ret = 1;
} else
ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach);
up(&dev->sem);
return ret;
}
回顾一下,上文添加驱动的处理函数是bus_for_each_dev,是遍历设备。而这里是bus_for_each_drv,是遍历驱动。这两个函数几乎一样,读者可以自行分析一下。
三 总结和引申
分析了platform这个简单总线,我们发现和input设备的管理方式类似。总线下面也有两个列表,一个是设备列表,一个是驱动列表。驱动注册时,要加入总线的驱动列表,同时在对应的设备列表里面寻找匹配的设备。
Platform总线虽然简单,但是引入了总线的概念。在内核驱动中,总线的使用是很多的。驱动目录里面的scsi,usb,ieee1394,pcmcia都是总线类型,它们也都有统一的设备和驱动管理。读者可以分析一下这些总线是怎么处理设备注册和驱动注册的。
习题:读者可以尝试注册一个假总线,然后注册一个设备和驱动,看看sysfs目录是否正常,设备是否正常被驱动。
- 深入浅出linux之总线和设备(platform总线)
- Linux设备模型之platform总线
- Linux设备驱动模型之platform总线
- Linux设备模型之platform总线
- LINUX设备驱动之platform总线
- Linux设备驱动模型之platform总线
- Linux设备模型之platform总线
- Linux设备模型之platform总线
- linux设备模型之platform总线
- linux设备模型之platform总线
- Linux设备模型之platform总线
- Linux设备模型之platform总线
- LINUX设备驱动之platform总线
- LINUX设备驱动之platform总线
- Linux设备模型之platform总线
- Linux设备模型之platform总线
- linux设备模型之platform总线(转)
- Linux设备模型之platform总线
- 公元二零一二之个人规划
- poj 1122||zoj 1053 FDNY to the Rescue!(最短路)
- C++的CLR程序与C#程序对比(2)
- Linux-world-2012-January->9 (dnw2_for_linux(ubuntu)successful version)
- Linux+Apache+Mysql+PHP安装笔记
- 深入浅出linux之总线和设备(platform总线)
- 嵌入式开发:中断优先级的重要性
- 我的sourceinsight字体设置
- Azure Storage Note
- Visual Studio 11开发指南(1) Visual Studio 11简介与新特性
- WinPe装win7,硬盘安装ubuntu10.10,锐捷上网,笔记
- Visual Studio 11开发指南(2) Visual Studio 11放弃宏处理
- Visual Studio 11开发指南(3)Visual Studio 11开发SharePoint 2011程序
- linux下的C语言开发(动态库)