跳转到start_kernel中,与本题相关的主要是setup_arch()和rest_init(),其中前者执行的较早,而后者做为start_kernel执行的最后一个函数。
函数start_kernel()和rest_init()定义在kernel/init/main.c中,函数setup_arch()定义在kernel/arch/arch_name/kernel/setup.c中。
1.setup_arch()在.init.text中,会执行machine_desc.init_very_early和machine_desc.init_early,代码如下,
915void __init setup_arch(char **cmdline_p)
916{
917 struct machine_desc *mdesc;
918
919 unwind_init();
920
921 setup_processor();
922 mdesc = setup_machine_fdt(__atags_pointer);
923 if (!mdesc)
924 mdesc = setup_machine_tags(machine_arch_type);
925 machine_desc = mdesc;
926 machine_name = mdesc->name;
927
928 if (mdesc->soft_reboot)
929 reboot_setup("s");
930
931 init_mm.start_code = (unsigned long) _text;
932 init_mm.end_code = (unsigned long) _etext;
933 init_mm.end_data = (unsigned long) _edata;
934 init_mm.brk = (unsigned long) _end;
935
936 /* populate cmd_line too for later use, preserving boot_command_line */
937 strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);
938 *cmdline_p = cmd_line;
939
940 parse_early_param();
941
942 if (mdesc->init_very_early)
943 mdesc->init_very_early();
944
945 sanity_check_meminfo();
946 arm_memblock_init(&meminfo, mdesc);
947
948 paging_init(mdesc);
949 request_standard_resources(mdesc);
950
951 unflatten_device_tree();
952
953#ifdef CONFIG_SMP
954 if (is_smp())
955 smp_init_cpus();
956#endif
957 reserve_crashkernel();
958
959 cpu_init();
960 tcm_init();
961
962#ifdef CONFIG_MULTI_IRQ_HANDLER
963 handle_arch_irq = mdesc->handle_irq;
964#endif
965
966#ifdef CONFIG_VT
967#if defined(CONFIG_VGA_CONSOLE)
968 conswitchp = &vga_con;
969#elif defined(CONFIG_DUMMY_CONSOLE)
970 conswitchp = &dummy_con;
971#endif
972#endif
973 early_trap_init();
974
975 if (mdesc->init_early)
976 mdesc->init_early();
977}
2. rest_init()执行后,创建内核线程kernel_init,这也是内核第一个线程,kernel_init做的与本题相关的初始化是do_pre_smp_initcalls()和do_basic_setup(),然后变成用户态进程init。代码如下,
841static int __init kernel_init(void * unused)
842{
843 /*
844 * Wait until kthreadd is all set-up.
845 */
846 wait_for_completion(&kthreadd_done);
847 /*
848 * init can allocate pages on any node
849 */
850 set_mems_allowed(node_states[N_HIGH_MEMORY]);
851 /*
852 * init can run on any cpu.
853 */
854 set_cpus_allowed_ptr(current, cpu_all_mask);
855
856 cad_pid = task_pid(current);
857
858 smp_prepare_cpus(setup_max_cpus);
859
860 do_pre_smp_initcalls();
861 lockup_detector_init();
862
863 smp_init();
864 sched_init_smp();
865
866 do_basic_setup();
867
868 /* Open the /dev/console on the rootfs, this should never fail */
869 if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
870 printk(KERN_WARNING "Warning: unable to open an initial console.\n");
871
872 (void) sys_dup(0);
873 (void) sys_dup(0);
874 /*
875 * check if there is an early userspace init. If yes, let it do all
876 * the work
877 */
878
879 if (!ramdisk_execute_command)
880 ramdisk_execute_command = "/init";
881
882 if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
883 ramdisk_execute_command = NULL;
884 prepare_namespace();
885 }
886
887 /*
888 * Ok, we have completed the initial bootup, and
889 * we're essentially up and running. Get rid of the
890 * initmem segments and start the user-mode stuff..
891 */
892
893 init_post();
894 return 0;
895}
do_pre_smp_initcalls()做.initcallearly.init section函数的执行,早于.initcall0.init-.initcall7.init;在初始化完smp调度后,.initcalln.init函数在do_basic_setup()被调用。
do_pre_smp_initcalls()代码如下,
786static void __init do_pre_smp_initcalls(void)
787{
788 initcall_t *fn;
789
790 for (fn = __initcall_start; fn < __initcall0_start; fn++)
791 do_one_initcall(*fn);
792}
do_basic_setup()及调用.initcalln.init的代码如下,
743static void __init do_initcall_level(int level)
744{
745 extern const struct kernel_param __start___param[], __stop___param[];
746 initcall_t *fn;
747
748 strcpy(static_command_line, saved_command_line);
749 parse_args(initcall_level_names[level],
750 static_command_line, __start___param,
751 __stop___param - __start___param,
752 level, level,
753 repair_env_string);
754
755 for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
756 do_one_initcall(*fn);
757}
758
759static void __init do_initcalls(void)
760{
761 int level;
762
763 for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
764 do_initcall_level(level);
765}
766
767/*
768 * Ok, the machine is now initialized. None of the devices
769 * have been touched yet, but the CPU subsystem is up and
770 * running, and memory and process management works.
771 *
772 * Now we can finally start doing some real work..
773 */
774static void __init do_basic_setup(void)
775{
776 cpuset_init_smp();
777 usermodehelper_init();
778 shmem_init();
779 driver_init();
780 init_irq_proc();
781 do_ctors();
782 usermodehelper_enable();
783 do_initcalls();
784}
各.initcalln.init的宏定义如下
168/* initcalls are now grouped by functionality into separate
169 * subsections. Ordering inside the subsections is determined
170 * by link order.
171 * For backwards compatibility, initcall() puts the call in
172 * the device init subsection.
173 *
174 * The `id' arg to __define_initcall() is needed so that multiple initcalls
175 * can point at the same handler without causing duplicate-symbol build errors.
176 */
177
178#define __define_initcall(level,fn,id) \
179 static initcall_t __initcall_##fn##id __used \
180 __attribute__((__section__(".initcall" level ".init"))) = fn
181
182/*
183 * Early initcalls run before initializing SMP.
184 *
185 * Only for built-in code, not modules.
186 */
187#define early_initcall(fn) __define_initcall("early",fn,early)
188
189/*
190 * A "pure" initcall has no dependencies on anything else, and purely
191 * initializes variables that couldn't be statically initialized.
192 *
193 * This only exists for built-in code, not for modules.
194 */
195#define pure_initcall(fn) __define_initcall("0",fn,0)
196
197#define core_initcall(fn) __define_initcall("1",fn,1)
198#define core_initcall_sync(fn) __define_initcall("1s",fn,1s)
199#define postcore_initcall(fn) __define_initcall("2",fn,2)
200#define postcore_initcall_sync(fn) __define_initcall("2s",fn,2s)
201#define arch_initcall(fn) __define_initcall("3",fn,3)
202#define arch_initcall_sync(fn) __define_initcall("3s",fn,3s)
203#define subsys_initcall(fn) __define_initcall("4",fn,4)
204#define subsys_initcall_sync(fn) __define_initcall("4s",fn,4s)
205#define fs_initcall(fn) __define_initcall("5",fn,5)
206#define fs_initcall_sync(fn) __define_initcall("5s",fn,5s)
207#define rootfs_initcall(fn) __define_initcall("rootfs",fn,rootfs)
208#define device_initcall(fn) __define_initcall("6",fn,6)
209#define device_initcall_sync(fn) __define_initcall("6s",fn,6s)
210#define late_initcall(fn) __define_initcall("7",fn,7)
211#define late_initcall_sync(fn) __define_initcall("7s",fn,7s)
212
213#define __initcall(fn) device_initcall(fn)
214
215#define __exitcall(fn) \
216 static exitcall_t __exitcall_##fn __exit_call = fn
217
218#define console_initcall(fn) \
219 static initcall_t __initcall_##fn \
220 __used __section(.con_initcall.init) = fn
221
222#define security_initcall(fn) \
223 static initcall_t __initcall_##fn \
224 __used __section(.security_initcall.init) = fn
machine_desc.init_machine在customize_machine() @ kernel/arch/arm/kernel/setup.c中被调用,而customize_machine指针引用在.initcall3.init中。customize_machine()代码如下,
842static int __init customize_machine(void)
843{
844 /* customizes platform devices, or adds new ones */
845 if (machine_desc->init_machine)
846 machine_desc->init_machine();
847 return 0;
848}
849arch_initcall(customize_machine);
各module_init(fn)设备驱动在.initcall6.init中被调用,由以下module_init的宏定义可以看出,
259/**
260 * module_init() - driver initialization entry point
261 * @x: function to be run at kernel boot time or module insertion
262 *
263 * module_init() will either be called during do_initcalls() (if
264 * builtin) or at module insertion time (if a module). There can only
265 * be one per module.
266 */
267#define module_init(x) __initcall(x);
而__initcall(x)宏定义为
213#define __initcall(fn) device_initcall(fn)
综上,题中提及各初始化的顺序是machine_desc.init_very_early, machine_desc.init_early, .initcallearly.init, machine_desc.init_machine(.initcall3.init), module_init(.initcall6.init) 。
[End]
[参考文章]
http://blog.csdn.net/paomadi/article/details/8611408
一、定义
#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 \
};
MACHINE_START和MACHINE_END框起了一个machine_desc结构体的声明并根据MACHINE_START宏的参数初始化其.nr和.name成员
并将该结构体标记编译到.arch.info.init段
在MACHINE_START和MACHINE_END宏之间可以初始化machine_desc结构体的剩余成员
machine_desc结构体的定义
struct machine_desc {
unsigned int nr; /* architecture number 编号 */
const char *name; /* architecture name 名字 */
unsigned long boot_params; /* tagged list */
unsigned int nr_irqs; /* number of IRQs 中断数 */
unsigned int video_start; /* start of video RAM */
unsigned int video_end; /* end of video RAM */
unsigned int reserve_lp0 :1; /* never has lp0 */
unsigned int reserve_lp1 :1; /* never has lp1 */
unsigned int reserve_lp2 :1; /* never has lp2 */
unsigned int soft_reboot :1; /* soft reboot */
void (*fixup)(struct machine_desc *,struct tag *, char **,struct meminfo *);
void (*reserve)(void); /* reserve mem blocks */
void (*map_io)(void); /* IO mapping function io映射函数 */
void (*init_irq)(void); /* 中断初始化函数 */
struct sys_timer *timer; /* system tick timer 滴答定时器 */
void (*init_machine)(void); /* 初始化函数 */
};
使用例子:
MACHINE_START(SMDKC110, "SMDKC110")
/* Maintainer: Kukjin Kim <kgene.kim@samsung.com> */
.boot_params = S5P_PA_SDRAM + 0x100,
.init_irq = s5pv210_init_irq,
.map_io = smdkc110_map_io,
.init_machine = smdkc110_machine_init,
.timer = &s3c24xx_timer,
MACHINE_END
这里smdkc110_machine_init就是对应的板级初始化函数,s5pv210_init_irq就是板级中断初始化函数,smdkc110_map_io就是板级io初始化函数...
二、调用关系
MACHINE_START宏将machine_desc标记编译到.arch.info.init段, 而/arch/arm/kernel/vmlinux.lds中
__arch_info_begin = .;
*(.arch.info.init)
__arch_info_end = .;
当系统启动时在linux启动函数start_kernel中调用了setup_arch(&command_line);
void __init setup_arch(char **cmdline_p)
{
struct tag *tags = (struct tag *)&init_tags;
struct machine_desc *mdesc; //声明了一个machine_desc结构体指针
char *from = default_command_line;
init_tags.mem.start = PHYS_OFFSET;
unwind_init();
setup_processor();
mdesc = setup_machine(machine_arch_type); //0根据machine_arch_type获取machine_desc
machine_name = mdesc->name; //设置名字
if (mdesc->soft_reboot) //需要软重启?
reboot_setup("s");
if (__atags_pointer)
tags = phys_to_virt(__atags_pointer);
else if (mdesc->boot_params) { //处理启动参数
#ifdef CONFIG_MMU
if (mdesc->boot_params < PHYS_OFFSET ||
mdesc->boot_params >= PHYS_OFFSET + SZ_1M) {
printk(KERN_WARNING"Default boot params at physical 0x%08lx out of reach\n",mdesc->boot_params);
} else
#endif
{
tags = phys_to_virt(mdesc->boot_params);
}
}
#if defined(CONFIG_DEPRECATED_PARAM_STRUCT)
if (tags->hdr.tag != ATAG_CORE)
convert_to_tag_list(tags);
#endif
if (tags->hdr.tag != ATAG_CORE)
tags = (struct tag *)&init_tags;
if (mdesc->fixup) //若存在fixup方法则调用其方法
mdesc->fixup(mdesc, tags, &from, &meminfo);
if (tags->hdr.tag == ATAG_CORE) {
if (meminfo.nr_banks != 0)
squash_mem_tags(tags);
save_atags(tags);
parse_tags(tags);
}
init_mm.start_code = (unsigned long) _text;
init_mm.end_code = (unsigned long) _etext;
init_mm.end_data = (unsigned long) _edata;
init_mm.brk = (unsigned long) _end;
/* parse_early_param needs a boot_command_line */
strlcpy(boot_command_line, from, COMMAND_LINE_SIZE);
/* populate cmd_line too for later use, preserving boot_command_line */
strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);
*cmdline_p = cmd_line;
parse_early_param();
arm_memblock_init(&meminfo, mdesc); //这里可能会调用reserve方法
paging_init(mdesc); //->devicemaps_init(mdesc)->map_io方法
request_standard_resources(&meminfo, mdesc); //这里可能会调用video_start方法
#ifdef CONFIG_SMP
if (is_smp())
smp_init_cpus();
#endif
reserve_crashkernel();
cpu_init();
tcm_init();
arch_nr_irqs = mdesc->nr_irqs; //1设置全局变量 中断个数
init_arch_irq = mdesc->init_irq; //2设置全局变量 中断初始化函数
system_timer = mdesc->timer; //3设置全局变量 sys_timer结构体
init_machine = mdesc->init_machine; //4设置全局变量 板级初始化函数
#ifdef CONFIG_VT
#if defined(CONFIG_VGA_CONSOLE)
conswitchp = &vga_con;
#elif defined(CONFIG_DUMMY_CONSOLE)
conswitchp = &dummy_con;
#endif
#endif
early_trap_init();
}
0. 结构machine_desc安装
static struct machine_desc * __init setup_machine(unsigned int nr)
{
extern struct machine_desc __arch_info_begin[], __arch_info_end[];
struct machine_desc *p;
for (p = __arch_info_begin; p < __arch_info_end; p++)//遍历__arch_info_begin和__arch_info_end之间的machine_desc结构体
if (nr == p->nr) { //找到对应的板
printk("Machine: %s\n", p->name);//打印板级信息
return p;
}
early_print("\n"
"Error: unrecognized/unsupported machine ID (r1 = 0x%08x).\n\n"
"Available machine support:\n\nID (hex)\tNAME\n", nr);
for (p = __arch_info_begin; p < __arch_info_end; p++)
early_print("%08x\t%s\n", p->nr, p->name);
early_print("\nPlease check your kernel config and/or bootloader.\n");
while (true)
/* can't use cpu_relax() here as it may require MMU setup */;
}
1.中断个数
start_kernel->early_irq_init->arch_probe_nr_irqs函数中nr_irqs = arch_nr_irqs ? arch_nr_irqs : NR_IRQS;设置全局nr_irqs变量
2.中断初始化函数
start_kernel->init_IRQ->init_arch_irq()
3.sys_timer结构体
start_kernel->time_init()调用system_timer->init()方法既sys_timer->init()
4.板级初始化函数
static void (*init_machine)(void) __initdata;
static int __init customize_machine(void)
{
/* customizes platform devices, or adds new ones */
if (init_machine)//全局函数init_machine存在
init_machine();//则调用,既mdesc->init_machine()
return 0;
}
arch_initcall(customize_machine);//用arch_initcall修饰customize_machine函数
arch_iniitcall函数在/include/linux/init.h中定义
#define arch_initcall(fn) __define_initcall("3",fn,3)
__define_initcall的定义
#define __define_initcall(level,fn,id) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" level ".init"))) = fn
展开就是static initcall_t __initcall_customize_machine3 __used __attribute__((__section__(".initcall3.init")))=customize_machine
在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 = .;
标注为.initcall3.init的函数编译进__initcall_start和__initcall_end框起的section中
而在系统启动的时候start_kernel->rest_init()->kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);创建了kernel线程
kernel_init->do_pre_smp_initcalls()
static void __init do_pre_smp_initcalls(void)
{
initcall_t *fn;
for (fn = __initcall_start; fn < __early_initcall_end; fn++)
do_one_initcall(*fn);
}
该函数遍历__initcall_start和__early_initcall_end中的函数,并调用do_one_initcall
int __init_or_module do_one_initcall(initcall_t fn)
{
int count = preempt_count();
int ret;
if (initcall_debug)
ret = do_one_initcall_debug(fn);
else
ret = fn();//执行了fn函数也就是customize_machine
msgbuf[0] = 0;
if (ret && ret != -ENODEV && initcall_debug)
sprintf(msgbuf, "error code %d ", ret);
if (preempt_count() != count) {
strlcat(msgbuf, "preemption imbalance ", sizeof(msgbuf));
preempt_count() = count;
}
if (irqs_disabled()) {
strlcat(msgbuf, "disabled interrupts ", sizeof(msgbuf));
local_irq_enable();
}
if (msgbuf[0]) {
printk("initcall %pF returned with %s\n", fn, msgbuf);
}
return ret;
}
http://blog.csdn.net/cxw3506/article/details/8475965
Machine定义以MACHINE_START开始并以MACHINE_END结束,如下mini2440开发板的移植为示例
MACHINE_START(MINI2440, "MINI2440")
.phys_io = S3C2410_PA_UART,
.io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
.boot_params = S3C2410_SDRAM_PA + 0x100,
.map_io = mini2440_map_io,
.init_machine = mini2440_init,
.init_irq = s3c24xx_init_irq,
.timer = &s3c24xx_timer,
MACHINE_END
MACHINE_START、MACHINE_END都是定义的宏,代码如下
/*
* Set of macros to define architecture features. This is built into
* a table by the linker.
*/
#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 \
};
这两个宏一起定义了一个类型为struct machine_desc的变量,结构体定义如下
struct machine_desc {
/*
* Note! The first four elements are used
* by assembler code in head.S, head-common.S
*/
unsigned int nr; /* architecture number */
unsigned int phys_io; /* start of physical io */
unsigned int io_pg_offst; /* byte offset for io
* page tabe entry */
const char *name; /* architecture name */
unsigned long boot_params; /* tagged list */
unsigned int video_start; /* start of video RAM */
unsigned int video_end; /* end of video RAM */
unsigned int reserve_lp0 :1; /* never has lp0 */
unsigned int reserve_lp1 :1; /* never has lp1 */
unsigned int reserve_lp2 :1; /* never has lp2 */
unsigned int soft_reboot :1; /* soft reboot */
void (*fixup)(struct machine_desc *,
struct tag *, char **,
struct meminfo *);
void (*map_io)(void);/* IO mapping function */
void (*init_irq)(void);
struct sys_timer *timer; /* system tick timer */
void (*init_machine)(void);
};
这个类型的变量放在内核代码段.arch.info.init中,在内核运行初期,被函数lookup_machine_type(此函数用汇编实现,在汇编文件中)取出,读取流程为
Start_kernel() -> setup_arch() -> setup_machine() -> lookup_machine_type()
在函数setup_machine()中,利用这个结构体类型的变量初始化一些全局变量,以备内核运行时使用,比如
init_arch_irq = mdesc->init_irq;
system_timer = mdesc->timer;
init_machine = mdesc->init_machine;
这个结构体中,成员init_machine保存的是开发板资源注册的初始化代码,init_irq保存的是中断初始化指针,timer保存的是一个struct sys_timer类型的指针…..如果我们要给自己的开发板定制内核,那么我们必须自己实现以上成员函数,其中函数init_machine()是我们向内核传递开发板设备信息的重要的常规途径,分析mini2440开发板内核移植代码知道,在这个函数中,注册了开发板所用到的所有设备的相关硬件信息!
static void __init mini2440_init(void)
{
struct mini2440_features_t features = { 0 };
int i;
printk(KERN_INFO "MINI2440: Option string mini2440=%s\n",
mini2440_features_str);
/* Parse the feature string */
mini2440_parse_features(&features, mini2440_features_str);
/* turn LCD on */
s3c_gpio_cfgpin(S3C2410_GPC(0), S3C2410_GPC0_LEND);
/* Turn the backlight early on */
WARN_ON(gpio_request(S3C2410_GPG(4), "backlight"));
gpio_direction_output(S3C2410_GPG(4), 1);
/* remove pullup on optional PWM backlight -- unused on 3.5 and 7"s */
s3c_gpio_setpull(S3C2410_GPB(1), S3C_GPIO_PULL_UP);
s3c2410_gpio_setpin(S3C2410_GPB(1), 0);
s3c_gpio_cfgpin(S3C2410_GPB(1), S3C2410_GPIO_INPUT);
/* Make sure the D+ pullup pin is output */
WARN_ON(gpio_request(S3C2410_GPC(5), "udc pup"));
gpio_direction_output(S3C2410_GPC(5), 0);
/* mark the key as input, without pullups (there is one on the board) */
for (i = 0; i < ARRAY_SIZE(mini2440_buttons); i++) {
s3c_gpio_setpull(mini2440_buttons[i].gpio, S3C_GPIO_PULL_UP);
s3c_gpio_cfgpin(mini2440_buttons[i].gpio, S3C2410_GPIO_INPUT);
}
if (features.lcd_index != -1) {
int li;
mini2440_fb_info.displays =
&mini2440_lcd_cfg[features.lcd_index];
printk(KERN_INFO "MINI2440: LCD");
for (li = 0; li < ARRAY_SIZE(mini2440_lcd_cfg); li++)
if (li == features.lcd_index)
printk(" [%d:%dx%d]", li,
mini2440_lcd_cfg[li].width,
mini2440_lcd_cfg[li].height);
else
printk(" %d:%dx%d", li,
mini2440_lcd_cfg[li].width,
mini2440_lcd_cfg[li].height);
printk("\n");
s3c24xx_fb_set_platdata(&mini2440_fb_info);
}
s3c24xx_udc_set_platdata(&mini2440_udc_cfg);
s3c24xx_mci_set_platdata(&mini2440_mmc_cfg);
s3c_nand_set_platdata(&mini2440_nand_info);
s3c_i2c0_set_platdata(NULL);
i2c_register_board_info(0, mini2440_i2c_devs,
ARRAY_SIZE(mini2440_i2c_devs));
platform_add_devices(mini2440_devices, ARRAY_SIZE(mini2440_devices));
if (features.count) /* the optional features */
platform_add_devices(features.optional, features.count);
}
那么成员函数init_machine什么时候被调用呢?
在函数setup_machine()中有一条语句init_machine = mdesc->init_machine;其中init_machine为全局函数指针变量,此变量在函数customize_machine()中被调用,代码如下所示:
static int __init customize_machine(void)
{
/* customizes platform devices, or adds new ones */
if (init_machine)
init_machine();
return 0;
}
arch_initcall(customize_machine);