【深入Linux内核驱动】CLK_OF_DECLARE使用及其内在机制
在clk驱动中有出现这样的一句宏定义调用
CLK_OF_DECLARE(s3c2410_clk, "samsung,s3c2410-clock", s3c2410_clk_init)
通过查找kernel路径下,发现没有其他地方去调用这个s3c2410_clk
,s3c2410_clk_init
.很疑惑不知道这个接口是怎么被调用的.
CLK_OF_DECLARE
通过百度查找,发现这个宏经常会在系统时钟初始化的时候进行使用:
Declare the compatible clocks and associate it with an initialization function using CLK_OF_DECLARE
使用
CLK_OF_DECLARE
声明兼容时钟并将其与初始化函数关联
CLK_OF_DECLARE 定义
#ifdef CONFIG_OF
#define _OF_DECLARE(table, name, compat, fn, fn_type) \
static const struct of_device_id __of_table_##name \
__used __section(__##table##_of_table) \
= { .compatible = compat, \
.data = (fn == (fn_type)NULL) ? fn : fn }
#else
#define _OF_DECLARE(table, name, compat, fn, fn_type) \
static const struct of_device_id __of_table_##name \
__attribute__((unused)) \
= { .compatible = compat, \
.data = (fn == (fn_type)NULL) ? fn : fn }
#endif
#define OF_DECLARE_1(table, name, compat, fn) \
_OF_DECLARE(table, name, compat, fn, of_init_fn_1)
#define OF_DECLARE_2(table, name, compat, fn) \
_OF_DECLARE(table, name, compat, fn, of_init_fn_2)
#define CLK_OF_DECLARE(name, compat, fn) OF_DECLARE_1(clk, name, compat, fn)
通过_OF_DECLARE
宏的实现发现其实CLK_OF_DECLARE
是声明定义的时钟信息.转化之后:
static const struct of_device_id __of_table_s3c2410_clk __used __section(__clk_of_table) {...}
由上可知是声明了一个struct of_device_id
类型的变量__of_table_s3c2410_clk
.但是后面的__section(__clk_of_table)
如何解释?
通过查询了解到:
section
关键字可以将变量定义到指定的输入段中。例如int a __attribute__((section(“list”))) = 0;
这句话的意思是将一个int型的变量a放到名为list的输入段中。
那么static const struct of_device_id __of_table_s3c2410_clk __used __section(__clk_of_table) {...}
即可理解为将一个struct of_device_id
类型的变量__of_table_s3c2410_clk
放入到名为__clk_of_table
的输入段中.
那为什么要这样操作,定义一个变量,最终再通过section
放入到指定的段中呢?
传统的应用编写时,每添加一个模块,都需要在main中添加新模块的初始化
使用
__attribute__((section()))
构建初始化函数表后,由模块告知main:“我要初始化“,添加新模块再也不需要在main代码中显式调用模块初始化接口。
以此实现main与模块之间的隔离,main不再关心有什么模块,模块的删减也不需要修改main。
内核其实就是使用__attribute__((section()))
从而实现的初始化函数表
1. module_init的定义
module_init
定义在<include/linux/init.h>
。代码如下:
代码中使用的“_section_”
,是一层层的宏,为了简化,把其等效理解为“section”
。
分析上述代码,我们发现module_init
由__attribute__((section(“name”)))
实现,把初始化函数地址保存到名为".initcall6.init"
的数据段中。
2. 链接内核使用自定义的链接脚本
我们看到内核目录最上层的Makefile
,存在如下代码:
# Rule to link vmlinux - also used during CONFIG_KALLSYMS
# May be overridden by arch/$(ARCH)/Makefile
quiet_cmd_vmlinux__ ?= LD $@
cmd_vmlinux__ ?= $(LD) $(LDFLAGS) $(LDFLAGS_vmlinux) -o $@ \
-T $(vmlinux-lds) $(vmlinux-init) \
--start-group $(vmlinux-main) --end-group \
$(filter-out $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) vmlinux.o FORCE ,$^)
本文的关注点在于:-T $(vmlinux-lds)
,通过“ld -T <script>”
使用了定制的链接脚本。定制的链接脚本在哪里呢?在Makefile存在如下代码:
vmlinux-lds := arch/$(SRCARCH)/kernel/vmlinux.lds
我们以”ARCH=arm“
为例,查看链接脚本:arch/arm/kernel/vmlinux.lds
:
在上述代码中,我们聚焦于两个地方:
__initcall6_start = .;
: 由__initcall6_start
指向当前地址
*(.initcall6.init)
: 所有.o
文件的.initcall6.init
数据段放到当前位置
如此,“__initcall6_start
”指向“.initcall6.init
”数据段的开始地址,在应用代码中就可通过“__initcall6_start
”访问数据段“.initcall6.init
”。
是不是如此呢?我们再聚焦到文件<init/main.c>
中。
在<init/main.c>
中,有如下代码:
static initcall_t *initcall_levels[] __initdata = {
__initcall0_start,
__initcall1_start,
__initcall2_start,
__initcall3_start,
__initcall4_start,
__initcall5_start,
__initcall6_start,
__initcall7_start,
__initcall_end,
};
......
int __init_or_module do_one_initcall(initcall_t fn)
{
......
if (initcall_debug)
ret = do_one_initcall_debug(fn);
else
ret = fn();
......
}
......
static void __init do_initcall_level(int level)
{
......
for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
do_one_initcall(*fn);
}
按0-7的初始化级别,依次调用各个级别的初始化函数表,而驱动module_init
的初始化级别为6。在“for (fn = initcall_levels[level]; fn <initcall_levels[level+1]; fn++)
”的for
循环调用中,实现了遍历当前初始化级别的所有初始化函数。
通过上述的代码追踪,我们发现module_init的实现有以下关键步骤:
- 通过
module_init
的宏,在编译时,把初始化函数放到了数据段:.initcall6.init
- 在链接成内核的时候,链接脚本规定好了
.initcall6.init
的数据段以及指向数据段地址的变量:_initcall6_start
- 在
init/main.c
中的for循环,通过_initcall6_start
的指针,调用了所有注册的驱动模块的初始化接口 - 最后通过
Kconfig/Makefile
选择编译的驱动,实现只要编译了驱动代码,则自动把驱动的初始化函数构建到统一的驱动初始化函数表
回到linux的clk子系统驱动里来,clk子系统的注册,初始化等函数操作与前述module_init
实现类似;我们在clk驱动中使用__section(__##table##_of_table)
对所用的时钟驱动进行声明初始化,并将该时钟传递给__clk_of_table
的内核时钟数据段中,后续内核在初始化时钟时,就会发现内核直接调用匹配好使用即可
/**
* of_clk_init() - Scan and init clock providers from the DT
* @matches: array of compatible values and init functions for providers.
*
* This function scans the device tree for matching clock providers
* and calls their initialization functions. It also does it by trying
* to follow the dependencies.
*/
void __init of_clk_init(const struct of_device_id *matches)
{
const struct of_device_id *match;
struct device_node *np;
struct clock_provider *clk_provider, *next;
bool is_init_done;
bool force = false;
LIST_HEAD(clk_provider_list);
if (!matches)
matches = &__clk_of_table;
/* First prepare the list of the clocks providers */
for_each_matching_node_and_match(np, matches, &match) {
struct clock_provider *parent;
本文重点在于咱们自己编写的clk子系统驱动如何传递给内核,内部如何传递,如何匹配,内核自定义链接脚本的这里不做过多叙述.
内容部分摘自:廖威雄: 利用__attribute__((section()))构建初始化函数表与Linux内核init的实现