Linux设备注册

在加载驱动之前,设备最先被加载。其中最主要的就是资源的注册了。本文简要介绍一些注册的一些过程。
最初,是要准备设备的资源,然后被初始化。
拿imx的MMC为例,其他的与之相同。
arch/arm/mach-imx/gener.c
static struct resource imx_mmc_resources[] = {
[0] = {
.start = 0x00214000,
.end = 0x002140FF,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = (SDHC_INT),
.end = (SDHC_INT),
.flags = IORESOURCE_IRQ,
},
};
Coment:设备的资源定义一般都位于arch/arm/mach-机器类型目录下面的文件中。

static struct platform_device imx_mmc_device = {
.name = "imx-mmc",
.id = 0,
.num_resources = ARRAY_SIZE(imx_mmc_resources),
.resource = imx_mmc_resources,
};

最初的设备定义仅此而已。

然后,看一下注册是经过怎样的流程:

void __init imx_map_io(void)
{
iotable_init(imx_io_desc, ARRAY_SIZE(imx_io_desc));
}

static struct platform_device *devices[] __initdata = {
&imx_mmc_device,
&imxfb_device,
&imx_uart1_device,
&imx_uart2_device,
};

static int __init imx_init(void)
{
return platform_add_devices(devices, ARRAY_SIZE(devices));
}

subsys_initcall(imx_init);

-----------------------------------------------------------------------------
以上调用imx_init,通过platform_add_devices来注册设备。我们继续跟进:

drivers/base/platform.c

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;
}
EXPORT_SYMBOL_GPL(platform_add_devices);

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

-------------------------------------------------------------------------
添加设备实际上也是注册设备,从其名字即可以看出来。
我们看看,platform_device_register里面调用了初始化和add两个函数。
初始化什么呢?因为在最初定义设备的时候,我们仅仅只定义了资源,设备成员变量dev没有被舒适化,那么这里 device_initialize(&pdev->dev);便是初始化这个设备了。
然后我们跟进 platform_device_add()
drivers/base/platform.c
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.%u", 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语句块中
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;
}
EXPORT_SYMBOL_GPL(platform_device_add);

这里最主要是两个函数,一个是insert_resource,一个是device_add.前一个把资源插入父设备的资源树中,关于Linux资源管理,网上有一篇很好的文章:“Linux对I/O端口资源的管理--詹荣开”。我摘录了其中对于insert_resource的描述:

  Linux是以一种倒置的树形结构来管理每一类I/O资源(如:I/O端口、外设内存、DMA和IRQ)的。每一类I/O资源都对应有一颗倒置的资源树,树中的每一个节点都是一个resource结构,而树的根结点root则描述了该类资源的整个资源空间。

insert_resource调用__request_resource()来注册相应的资源:
/* Return the conflict entry if you can't request it */
static struct resource * __request_resource(struct resource *root, struct resource *new)
{
resource_size_t start = new->start;
resource_size_t end = new->end;
struct resource *tmp, **p;

if (end < start)
return root;
if (start < root->start)
return root;
if (end > root->end)
return root;
p = &root->child;
for (;;) {
tmp = *p;
if (!tmp || tmp->start > end) {
new->sibling = tmp;
*p = new;
new->parent = root;
return NULL;
}
p = &tmp->sibling;
if (tmp->end < start)
continue;
return tmp;
}
}
算法描述:

  ①前三个if语句判断new所描述的资源范围是否被包含在root内,以及是否是一段有效的资源(因为end必须大于start)。否则就返回root指针,表示与根结点相冲突。
  ②接下来用一个for循环遍历根节点root的child链表,以便检查是否有资源冲突,并将new插入到child链表中的合适位置(child链表是以I/O资源物理地址从低到高的顺序排列的)。为此,它用tmp指针指向当前正被扫描的resource结构,用指针p指向前一个resource结构的sibling指针成员变量,p的初始值为指向root->sibling。For循环体的执行步骤如下:
  l 让tmp指向当前正被扫描的resource结构(tmp=*p)。
  l 判断tmp指针是否为空(tmp指针为空说明已经遍历完整个child链表),或者当前被扫描节点的起始位置start是否比new的结束位置end还要大。只要这两个条件之一成立的话,就说明没有资源冲突,于是就可以把new链入child链表中:①设置new的sibling指针指向当前正被扫描的节点tmp(new->sibling=tmp);②当前节点tmp的前一个兄弟节点的sibling指针被修改为指向new这个节点(*p=new);③将new的parent指针设置为指向root。然后函数就可以返回了(返回值NULL表示没有资源冲突)。
  l 如果上述两个条件都不成立,这说明当前被扫描节点的资源域有可能与new相冲突(实际上就是两个闭区间有交集),因此需要进一步判断。为此它首先修改指针p,让它指向tmp->sibling,以便于继续扫描child链表。然后,判断tmp->end是否小于new->start,如果小于,则说明当前节点tmp和new没有资源冲突,因此执行continue语句,继续向下扫描child链表。否则,如果tmp->end大于或等于new->start,则说明tmp->[start,end]和new->[start,end]之间有交集。所以返回当前节点的指针tmp,表示发生资源冲突。


另外,看一下device_add()
drivers/base/core.c

/**
* device_add - add device to device hierarchy.
* @dev: device.
*
* This is part 2 of device_register(), though may be called
* separately _iff_ device_initialize() has been called separately.
*
* This adds it to the kobject hierarchy via kobject_add(), adds it
* to the global and sibling lists for the device, then
* adds it to the other relevant subsystems of the driver model.
*/
int device_add(struct device *dev)
{
struct device *parent = NULL;
char *class_name = NULL;
struct class_interface *class_intf;
int error = -EINVAL;

dev = get_device(dev);
if (!dev || !strlen(dev->bus_id))
goto Error;

pr_debug("DEV: registering device: ID = '%s'\n", dev->bus_id);

parent = get_device(dev->parent);

error = setup_parent(dev, parent);
if (error)
goto Error;

/* first, register with generic layer. */
kobject_set_name(&dev->kobj, "%s", dev->bus_id);
error = kobject_add(&dev->kobj);
if (error)
goto Error;

/* notify platform of device entry */
if (platform_notify)
platform_notify(dev);

/* notify clients of device entry (new way) */
if (dev->bus)
blocking_notifier_call_chain(&dev->bus->bus_notifier,
BUS_NOTIFY_ADD_DEVICE, dev);

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;
error = device_create_file(dev, &dev->uevent_attr);
if (error)
goto attrError;

if (MAJOR(dev->devt)) {
struct device_attribute *attr;
attr = kzalloc(sizeof(*attr), GFP_KERNEL);
if (!attr) {
error = -ENOMEM;
goto ueventattrError;
}
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 ueventattrError;
}

dev->devt_attr = attr;
}

if (dev->class) {
sysfs_create_link(&dev->kobj, &dev->class->subsys.kset.kobj,
"subsystem");
/* If this is not a "fake" compatible device, then create the
* symlink from the class to the device. */
if (dev->kobj.parent != &dev->class->subsys.kset.kobj)
sysfs_create_link(&dev->class->subsys.kset.kobj,
&dev->kobj, dev->bus_id);
if (parent) {
sysfs_create_link(&dev->kobj, &dev->parent->kobj,
"device");
#ifdef CONFIG_SYSFS_DEPRECATED
class_name = make_class_name(dev->class->name,
&dev->kobj);
if (class_name)
sysfs_create_link(&dev->parent->kobj,
&dev->kobj, class_name);
#endif
}
}

if ((error = device_add_attrs(dev)))
goto AttrsError;
if ((error = device_add_groups(dev)))
goto GroupError;
if ((error = device_pm_add(dev)))
goto PMError;
if ((error = bus_add_device(dev)))
goto BusError;
if (!dev->uevent_suppress)
kobject_uevent(&dev->kobj, KOBJ_ADD);
if ((error = bus_attach_device(dev)))
goto AttachError;
if (parent)
klist_add_tail(&dev->knode_parent, &parent->klist_children);

if (dev->class) {
down(&dev->class->sem);
/* tie the class to the device */
list_add_tail(&dev->node, &dev->class->devices);

/* notify any interfaces that the device is here */
list_for_each_entry(class_intf, &dev->class->interfaces, node)
if (class_intf->add_dev)
class_intf->add_dev(dev, class_intf);
up(&dev->class->sem);
}
Done:
kfree(class_name);
put_device(dev);
return error;
......

}

上面的这个语句块主要的工作是在设备模型的工作。从注释中,我们可以看到其主要思路。

评论

此博客中的热门博文

Linux/ARM Page Table Entry 属性设置分析

提交了30次才AC ---【附】POJ 2488解题报告

笔记