Linux设备驱动初始化流程
Linux设备驱动初始化的流程一直不是很清楚,今天仔细看了一下linux初始化部分的代码才真正的搞明白,记录下来。 做过驱动的同学都知道,在arch/arm/目录下有和板级配置相关的文件,我使用的是Fresscale i.MX28开发板,在arch/arm/mach-mx28/目录下有mx28evk.c文件,该文件中有开发板初始化需要调用的函数。MACHINE_STAR
Linux设备驱动初始化的流程一直不是很清楚,今天仔细看了一下linux初始化部分的代码才真正的搞明白,记录下来。
做过驱动的同学都知道,在arch/arm/目录下有和板级配置相关的文件,我使用的是Fresscale i.MX28开发板,在arch/arm/mach-mx28/目录下有mx28evk.c文件,该文件中有开发板初始化需要调用的函数。
MACHINE_START(MX28EVK, "Freescale MX28EVK board")
.phys_io = 0x80000000,
.io_pg_offst = ((0xf0000000) >> 18) & 0xfffc,
.boot_params = 0x40000100,
.fixup = fixup_board,
.map_io = mx28_map_io,
.init_irq = mx28_irq_init,
.init_machine = mx28evk_init_machine,
.timer = &mx28_timer.timer,
MACHINE_END
看MACHINE_START的定义才发现,上面的这几行代码其实就是定义一个 machine_desc的结构体变量,并对其中部分成员赋值。
#define MACHINE_START(_type,_name) \
static const struct machine_desc __mach_desc_##_type \
__used \
__attribute__((__section__(".arch.info.init"))) = { \
.nr = MACH_TYPE_##_type, \
.name = _name,
#define MACHINE_END \
};
函数mx28evk_init_machine主要初始化开发板的一些gpio、特殊引脚、平台设备,并将这些平台设备添加到内核。这个函数名被赋值给init_machine这个指针。
static void __init mx28evk_init_machine(void)
{
mx28_pinctrl_init();
/* Init iram allocate */
#ifdef CONFIG_VECTORS_PHY_ADDR
/* reserve the first page for irq vector table*/
iram_init(MX28_OCRAM_PHBASE + PAGE_SIZE, MX28_OCRAM_SIZE - PAGE_SIZE);
#else
iram_init(MX28_OCRAM_PHBASE, MX28_OCRAM_SIZE);
#endif
mx28_gpio_init();
mx28evk_pins_init();
mx28_device_init();
mx28evk_device_init();
}
我现在要关注的就是这个函数在什么地方调用,查阅源码发现在arch/arm/kernel/setup.c文件中的 setup_arch函数中对init_machine有赋值操作。仔细阅读源码
void __init setup_arch(char **cmdline_p)
{
struct tag *tags = (struct tag *)&init_tags;
struct machine_desc *mdesc;
char *from = default_command_line;
unwind_init();
setup_processor();
mdesc = setup_machine(machine_arch_type);
machine_name = mdesc->name;
*******************(省略部分代码)
/*
* Set up various architecture-specific pointers
*/
init_arch_irq = mdesc->init_irq;
system_timer = mdesc->timer;
init_machine = mdesc->init_machine;
early_trap_init();
}
通过setup_machine获得这个开发板对应的machine_desc结构体变量,machine_arch_type其实是一个宏,定义在include/generated/mach-types.h,这个文件是编译生成的。
#ifdef CONFIG_MACH_MX28EVK
# ifdef machine_arch_type
# undef machine_arch_type
# define machine_arch_type __machine_arch_type
# else
# define machine_arch_type MACH_TYPE_MX28EVK
# endif
# define machine_is_mx28evk() (machine_arch_type == MACH_TYPE_MX28EVK)
#else
# define machine_is_mx28evk() (0)
#endif
MACH_TYPE_MX28EVK宏为
#define MACH_TYPE_MX28EVK 2531
在setup_arch函数的后面将mdesc->init_machine赋值给函数指针init_machine
static void (*init_machine)(void) __initdata;
static int __init customize_machine(void)
{
/* customizes platform devices, or adds new ones */
if (init_machine)
init_machine();
return 0;
}
arch_initcall(customize_machine);
看到这里终于知道mx28evk_init_machine函数是在哪里调用的了
但它没有被明显的调用,而是被放进arch_initcall()里面,去看看arch_initcall()是怎么定义的。在include/linux/init.h文件中有
#define arch_initcall(fn) __define_initcall("3",fn,3)
#define __define_initcall(level,fn,id) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" level ".init"))) = fn
凭着看代码的经验,customize_machine函数被放在.initcall3.init里,.initcall3.init定义在arch/arm/kernel/vmlinux.lds中
__initcall_start = .;
*(.initcallearly.init) __early_initcall_end = .;
*(.initcall0.init) *(.initcall0s.init)
*(.initcall1.init) *(.initcall1s.init)
*(.initcall2.init) *(.initcall2s.init)
*(.initcall3.init) *(.initcall3s.init)
*(.initcall4.init) *(.initcall4s.init)
*(.initcall5.init) *(.initcall5s.init)
*(.initcallrootfs.init)
*(.initcall6.init) *(.initcall6s.init)
*(.initcall7.init) *(.initcall7s.init)
__initcall_end = .;
在init/main.c do_initcalls函数中被调用
extern initcall_t __initcall_start[], __initcall_end[], __early_initcall_end[];
static void __init do_initcalls(void)
{
initcall_t *fn;
for (fn = __early_initcall_end; fn < __initcall_end; fn++)
do_one_initcall(*fn);
/* Make sure there is no pending stuff from the initcall sequence */
flush_scheduled_work();
}
do_initcalls中有一个for循环,customize_machine在这里被调用,也就是说mx28evk_init_machine函数在这for循环期间被调用。mx28evk_init_machine函数执行后,platform_device全部被添加到内核中。接下来就该看看driver是在什么时候加载的。
还记得我们写驱动时都要使用 module_init()将driver入口函数告诉内核。其实module_init是一个宏,也定义在include/linux/init.h文件中
/**
* module_init() - driver initialization entry point
* @x: function to be run at kernel boot time or module insertion
*
* module_init() will either be called during do_initcalls() (if
* builtin) or at module insertion time (if a module). There can only
* be one per module.
*/
#define module_init(x) __initcall(x);
#define __initcall(fn) device_initcall(fn)
#define device_initcall(fn) __define_initcall("6",fn,6)
#define __define_initcall(level,fn,id) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" level ".init"))) = fn
根据上面这些代码我们知道,使用module_init()的驱动,入口函数被放在 .initcall6.init里,也是在init/main.c do_initcalls函数中被调用。相比platform_device,各种driver相对要靠后一点才会被调用。
总结一下:设备驱动初始化的大概流程是这样
Start_kernel()----->Reset_init()------->Kernel_init()------->do_basic_setup------->do_initcalls
更多推荐
所有评论(0)