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



 

 

                        

 

Logo

更多推荐