写在前面
哈哈,写东西前总喜欢先扯蛋,赶时间的直接无视这段吧。前段时间照着x_project成功的将手上的一块基于nuc972的板子成功移植上了最新的u-boot,相关细节可以参考我的这篇博客。
那篇博客的最后我给自己设立了几个后续要完善的功能,是选择继续模仿着搬砖呢,还是先把一些东西看明白些呢,显然我开始写这篇文章的时候是选择了后者,哈哈。
“慢下来,享受技术” —— 蜗窝科技。
依葫芦画瓢后的疑问
一直以来我大多是处于一种依葫芦画瓢的搬砖状态,同样的下面这部分code展示了我画的瓢,nuc972在u-boot上的串口驱动。
#include <common.h>
#include <asm/io.h>
#include <dm.h>
#include <serial.h>
#include "serial_nuc970.h"
static int nuc970_serial_setbrg(struct udevice *dev, int baudrate)
{
......
}
static int nuc970_serial_getc(struct udevice *dev)
{
......
}
static int nuc970_serial_putc(struct udevice *dev, const char c)
{
......
}
static int nuc970_serial_pending(struct udevice *dev, bool input)
{
......
}
static const struct dm_serial_ops nuc970_serial_ops = {
.putc = nuc970_serial_putc,
.pending = nuc970_serial_pending,
.getc = nuc970_serial_getc,
.setbrg = nuc970_serial_setbrg,
};
static int nuc970_serial_probe(struct udevice *dev)
{
......
}
U_BOOT_DRIVER(nuc970_serial) = {
.name = "nuc970_serial",
.id = UCLASS_SERIAL,
.ops = &nuc970_serial_ops,
.probe = nuc970_serial_probe,
.flags = DM_FLAG_PRE_RELOC,
};
U_BOOT_DEVICE(nuc970_serial) = {
.name = "nuc970_serial",
};
为什么code里写的是nuc970,不是说好了是nuc972的吗,因为我是从公版bsp里面沿用下来的,不用纠结,哈哈
那问题来了,device是怎么遇上driver的呢?这个问题似乎是终极问题,要回答它要先弄明白很多东西,那这篇文章我们先来看看宏U_BOOT_DRIVER
和U_BOOT_DEVICE
多做了些什么事,它们是怎么被用起来的。
一切问题的答案多会在code中找到,下面开始代码走起,哈哈
链接器为我们做的那些事
1、脱下宏的外衣
宏U_BOOT_DRIVER
和U_BOOT_DEVICE
展开得到如下:
struct driver _u_boot_list_2_driver_2_nuc970_serial __aligned(4) \
__attribute__((unused, section(".u_boot_list_2_driver_2_nuc970_serial"))) = {
.name = "nuc970_serial",
.id = UCLASS_SERIAL,
.ops = &nuc970_serial_ops,
.of_match = nuc970_serial_ids,
.probe = nuc970_serial_probe,
.flags = DM_FLAG_PRE_RELOC,
};
struct driver_info _u_boot_list_2_driver_info_2_nuc970_serial __aligned(4) \
__attribute__((unused, section(".u_boot_list_2_driver_info_2_nuc970_serial"))) = {
.name = "nuc970_serial",
};
从上面我们可以看到声明他们的时候对它们做了如下要求:
- 要求它们存放的时候4字节对齐,这通常是为了更方便的访问处理它们;
- 要求它们存放在一个各自独有的段里面。
那问题又来了,要求他们存放在各自的段里面那肯定是要在链接脚本里体现的啊,让我们赶紧来看下链接脚本。
让我们打开./arch/arm/cpu/u-boot.lds文件,仔细的看我们会看到一段这样的描述:
.............................
. = ALIGN(4);
.u_boot_list : {
KEEP(*(SORT(.u_boot_list*)));
}
.............................
没错所有以.u_boot_list开头的段多将在这里存放,还有两个奇怪的符号是要做啥呢,赶紧查查,这里是关键的地方千万不要放过。
google一番后了解到KEEP关键字是为了保证所有的段多被加进来,不要被链接器自作聪明的把某些它认为没有的段舍弃,事实上我们确实是定义了一些会让它看起来没用的段,这个我们后面会提到;而SORT关键字如其名字就是根据段名字串进行排序然后存放,它将起到关键作用,为了配合它我们把段名作了些手脚,你看前面的段名展开后这么长,哈哈
通过前面的窥探,我们大概知道了用宏U_BOOT_DRIVER
和U_BOOT_DEVICE
声明的变量将被分配到自己一个特有的段下,在链接的时候被组织到一起,那它们是怎么被用起来的呢,还是回到代码里来看看
2、回到起点
让从打开器件驱动模型后执行的第一个函数initf_dm()开始一路往下看去,一路展开如下:
initf_dm()
{
dm_init_and_scan()
{
dm_init()
{
device_bind_by_name(&root_info)
{
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))
return entry;
}
}
}
}
}
}
这里让我们先把注意力放到这句话上struct driver *drv = ll_entry_start(struct driver, driver);
可以看到它又是个宏定义,同样来自于头文件Linker_lists.h
让我们把它展开来一探究竟,如下:
struct driver *drv = ({
static char start[0] __aligned(4) __attribute__((unused, section(".u_boot_list_2_driver_1")));
(driver *)&start;
});
static char start[0]
是什么鬼,0个元素的字符数组,那就是不占用空间咯,还把它强加到了.u_boot_list_2_driver_1
命名的段上,原来它就是那个会让编译器看起来没有用的段,而在链接脚本里给它强加上KEEP关键字,我们再结合这个段名和(driver *)&start;
我想你应该能明白了是怎么一回事了吧,哈哈
有start那会不会有end的呢?让我们在Linker_lists.h
找下,果然有一宏叫ll_entry_end
那就让我们假设有这样一句代码来展开试试,如下:
struct driver *end_drv = ll_entry_start(struct driver, driver);
struct driver *end_drv = ({
static char end[0] __aligned(4) __attribute__((unused, section(".u_boot_list_2_driver_3")));
(driver *)&end;
});
让我们来关注下它的段名.u_boot_list_2_driver_3
取的多么的优雅,结合前面我们定义的nuc972的串口驱动,然后再结合链接脚本里SORT关键字的那么一处理,我想你大概明白它们是怎么被组织到一块的了吧,让我们来列列大概如下:
................................
.u_boot_list_2_driver_1
.u_boot_list_2_driver_2_nuc970_serial
.u_boot_list_2_driver_3
................................
哈哈,列好队了,随时待命。可以看出所有driver的结构体变量多被组织到这里,而因为段u_boot_list_2_driver_1
和u_boot_list_2_driver_3
的存在会给我提供一个找到它们的一个起始地址和结束地址。
小结
总结要简单,总结要简单,总结要简单,那在这篇文章里我们总结一句话,如下:
使用Linker_lists.h
文件提供的宏声明的相同类型的变量将被链接器安排到一起并被排序,同时它还提供了让我们找到它们的方法
哈哈,怎么变成分析Linker_lists.h
文件的作用了,说好的分析器件驱动模型的呢,不急后面我们慢慢看下去,这里先把它搞清楚;其实这些内容在Linker_lists.h
已经注释的非常清楚了,点赞,而这里是从code来一步步看清它的。