在上一篇链接器的秘密里面我们讲到我们用一些特殊的宏让链接器帮我们把一些初始化好的结构体列好队并安排在程序的某一个段里面,这里我例举出了三个和我们主题相关段的分布情况,它们大概如下图所示:(我们可以通过搜索宏ll_entry_declare
来找到它们)
那么问题来了,那它们三个是什么关系呢?它们在程序里面是怎么被用到的呢?
剧透环节希望你们喜欢,uclass对驱动进行了归类处理,它描述了具有相似方法的驱动,而不管它们的具体形式。比如对于GPIO它们会具有读取管脚和设置管脚输出的方法,对于serial它们会具有输出串行数据、读取串行数据和设置波特率等方法,这就是uclass要描述的东西,而它不会在乎GPIO或者serial是片内soc上的外设还是外部专用芯片扩展的。你还可以把它理解为一个具体驱动的基类。
让我们回到上图,让我们来理理它们三个的关系,uclass_driver即是uclass的描述,driver即是一个具体的device的驱动,driver_info其实是对应于一个具体的device,所以一个driver_info会有一个与之对应的driver它们通过相同的名字来找到对方。
在u-boot中有个重要的数据结构struct global_data
,通常我们用于全局访问的数据多会放在这个数据结构下,在这里我们要关注其中的struct udevice *dm_root;
成员和struct list_head uclass_root;
成员,它们会将所有这里介绍的东西串起来。
在开始之前我们先来看下uclass它的数据结构是怎样的,如下:
struct uclass {
void *priv;
struct uclass_driver *uc_drv;
struct list_head dev_head;
struct list_head sibling_node;
};
通过注释我们了解到dev_head这里将链接所有从属于这个驱动类的器件,sibling_node为了把所有的uclass链接起来并后面会添加到uclass_root链表,uc_drv则指向了描述这个驱动类具体方法的声明。
这里让我们回到code里面来吧,我们继续回到我们在链接器的秘密里面的一段code,这次我将整个过程关键的位置多展开来了,如下:
initf_dm()
{
dm_init_and_scan()
{
dm_init()
{
device_bind_by_name(&root_info, gd->dm_root)
{
struct driver *drv;
drv = lists_driver_lookup_name("root_driver")
{
struct driver *drv = ll_entry_start(struct driver, driver);
const int n_ents = ll_entry_count(struct driver, driver);
for (entry = drv; entry != drv + n_ents; entry++)
{
if (!strcmp(name, entry->name)) // ------------------------------- N.1
return entry;
}
};
device_bind_common(drv, "root_driver", gd->dm_root)
{
struct udevice *dev;
struct uclass *uc;
uclass_get(drv->id, &uc) // ------------------------------- N.2
{
struct uclass *uc;
uc = uclass_find(drv->id)
{
list_for_each_entry(uc, &gd->uclass_root, sibling_node) { // -- N.3
if (uc->uc_drv->id == key)
return uc;
}
};
if (!uc)
return uclass_add(drv->id, &uc)
{
struct uclass_driver *uc_drv;
struct uclass *uc;
uc_drv = lists_uclass_lookup(drv->id); // --------- N.4
uc = calloc(1, sizeof(*uc));
uc->uc_drv = uc_drv;
list_add(&uc->sibling_node, &gd->uclass_root); // --- N.5
&uc = uc;
};
};
dev = calloc(1, sizeof(struct udevice)); // --- N.6
dev->name = name;
dev->driver = drv;
dev->uclass = uc;
uclass_bind_device(dev)
{
struct uclass *uc;
uc = dev->uclass;
list_add_tail(&dev->uclass_node, &uc->dev_head); // --- N.7
};
if (devp) // --- N.8
*devp = dev;
};
}
}
}
}
我们一起来读一下这段展开的code就会看到整个device和driver连接的过程。
N.1的地方通过名字来找到对应的driver,这里就展示了是怎么通过device的名字来找到对应driver的了,这里我把它的入口参数里面的器件名字加上去了,可以看到这一次找的是root_driver,我们可以在文件root.c的最后看到它的声明。
N.2的地方先根据driver的ID(见uclass-id.h)来找到它属于的uclass,即找到它的从属,它先会在gd->uclass_root
链表里面去找见N.3处,显然如果是第一次找的话是找不到的,当找不到的时候它便会去预先声明的uclass_driver段里面找并分配出一个uclass结构添加到gd->uclass_root
链表里面去,见N.4、N.5处。
找到归属后就为自己分配一个udevice的空间,然后记住自己的名字和driver及从属的uclass见N.6,之后就是把自己添加到从属的uclass的device链表里去,见N.7。
到了N.8后我们的gd->dm_root就初始化好了,指向我们的root device,通过它我们可以找到uclass,找到uclass就可以找到从属于它的device,这就是他们的关系。
经过了上述的描述我们只是初始化好了root device,那其他device呢,它们也是通过一样的过程被一个个添加到链表里面来的,让我们从code上来看看,如下:
initf_dm()
{
dm_init_and_scan()
{
dm_init()
{
device_bind_by_name(&root_info, gd->dm_root)
{
lists_driver_lookup_name("root_driver");
device_bind_common(drv, "root_driver", gd->dm_root);
uclass_bind_device(dev);
};
};
dm_scan_platdata()
{
lists_bind_drivers(gd->dm_root)
{
struct driver_info *info =
ll_entry_start(struct driver_info, driver_info);
const int n_ents = ll_entry_count(struct driver_info, driver_info);
struct driver_info *entry;
struct udevice *dev;
int result = 0;
int ret;
for (entry = info; entry != info + n_ents; entry++) {
ret = device_bind_by_name(gd->dm_root, pre_reloc_only, entry, &dev);
if (ret && ret != -EPERM) {
dm_warn("No match for driver '%s'\n", entry->name);
if (!result || ret != -ENOENT)
result = ret;
}
}
return result;
};
};
}
}
可以看出在执行完dm_init()后它将去找到平台上所有的device并把他们的driver找到链接到链表里面去可以看出是device_bind_by_name完成了所有的工作。
为了比较直观的看到他们被相互连接的关系,我这里画了一张图,如下:
图中我假象了还有叫做“xxx_serial”的串口器件被加了进来,从中我们可以直观看出来struct uclass serial;
将作所有serial器件的“基类”它们多将被挂接到serial uclass的dev_head节点上,每个serial器件的uclass多是指向serial uclass的。到这里我想你应该对uclass的作用有了一定的理解了,废话就不多说了,下节再见。