PowerPC devtree
我们对kernel中dts的挂载设备到platform和PCI总线的流程进行解析。
我们先看uboot对于fdt的启动支持过程。
我们以uboot自动启动进行细述其过程
Uboot运行到board.c后,最终进入main_loop函数,在等待没有中止按键后运行如下:
s = getenv ("bootcmd");
run_command(s, 0);
由于我们在mpc8313erdb.h中定义bootcmd的宏为:
#define CONFIG_BOOTCOMMAND \
"bootm 0xfe080000 - 0xfed80000"
故我们调用run_command则运行的是bootm 0xfe080000 – 0xfed80000
我们知道其会调用到do_bootm函数接口
do_bootm分为如下几步:
1、 bootm_start:分别调用boot_get_kernel、boot_get_ramdisk和boot_get_fdt,并对kernel的头信息、ramdisk信息和fdt信息添加到bootm_headers_t结构中(其中boot_get_ramdisk和boot_get_fdt也会加载ramdisk和fdt到相应的内存位置)。
2、 bootm_load_os:解压kernel到指定内存空间
3、 根据加载的image类型,此处为linux,取出对应的跳转方式,并调用,运行到do_bootm_linux
do_bootm_linux:调用到boot_body_linux为最后的跳转做准备,最终调用boot_jump_linux调到kernel运行
boot_body_linux分为如下几步:
1、 调用boot_cmdline_linux,分配并加载bootargs参数
2、 调用boot_bd_t_linux,分配并加载板卡信息
3、 调用boot_ramdisk_high,调整ramdisk空间
4、 调用boot_relocate_fdt,调整fdt空间
5、 其后对fdt和板卡信息进行fixup,并追加chosen节点,将bootargs参数追加到chosen节点,之后调整fdt大小校验等。
head_32.S先调用early_init,确定CPU型号,并对各自CPU平台进行相关操作
notrace unsigned long __initearly_init(unsigned long dt_ptr)
{
unsignedlong offset = reloc_offset();
structcpu_spec *spec;
/*First zero the BSS -- use memset_io, some platforms don't have
* caches on yet */
memset_io((void__iomem *)PTRRELOC(&__bss_start), 0,
__bss_stop- __bss_start);
/*
* Identify the CPU type and fix up codesections
* that depend on which cpu we have.
*/
spec= identify_cpu(offset, mfspr(SPRN_PVR));
do_feature_fixups(spec->cpu_features,
PTRRELOC(&__start___ftr_fixup),
PTRRELOC(&__stop___ftr_fixup));
do_feature_fixups(spec->mmu_features,
PTRRELOC(&__start___mmu_ftr_fixup),
PTRRELOC(&__stop___mmu_ftr_fixup));
do_lwsync_fixups(spec->cpu_features,
PTRRELOC(&__start___lwsync_fixup),
PTRRELOC(&__stop___lwsync_fixup));
returnKERNELBASE + offset;
}
identify_cpu:根据CPU内置标志,在cputable中查找对应的cpu_spec,我们的cpu对应如下
{ /*e300c3 (e300c1, plus one IU, half cache size) on 83xx */
.pvr_mask = 0x7fff0000,
.pvr_value = 0x00850000,
.cpu_name = "e300c3",
.cpu_features = CPU_FTRS_E300,
.cpu_user_features = COMMON_USER,
.mmu_features = MMU_FTR_USE_HIGH_BATS |
MMU_FTR_NEED_DTLB_SW_LRU,
.icache_bsize = 32,
.dcache_bsize = 32,
.cpu_setup = __setup_cpu_603,
.num_pmcs = 4,
.oprofile_cpu_type = "ppc/e300",
.oprofile_type = PPC_OPROFILE_FSL_EMB,
.platform = "ppc603",
},
我们从head_32.S中start_here开始分析其流程,在start_here之前无非关于MMU,IBAT,中断向量表等构架相关的初始化,我们再次略过。
Start_hereàmachine_init()
notrace void __init machine_init(unsignedlong dt_ptr)
{
…
/*Enable early debugging if any specified (see udbg.h) */
udbg_early_init();
/*Do some early initialization based on the flat device tree */
early_init_devtree(__va(dt_ptr));
probe_machine();
….
}
udbg_early_init:支持早期直接操作调试串口寄存器进行打印输出
early_init_devtree:我们重点分析的devtree早期解析过程
probe_nachine:从内核支持的machine_desc中进行查找比对
下面我们继续分析early_init_devtree和probe_nachine
early_init_devtree主要分析运行时参数,运行时参数经过上述分析,uboot将bootargs运行时参数掺杂在chosen节点中,具体的动作就是获取chosen node的bootargs、initrd等属性的value,并将其保存在全局变量(boot_command_line,initrd_start、initrd_end)中。使用tag list方法是类似的,通过分析tag list,获取相关信息,保存在同样的全局变量中。
void __init early_init_devtree(void*params)
{
phys_addr_tlimit;
/*Setup flat device-tree pointer */
initial_boot_params= params;
/*Retrieve various informations from the /chosen node of the
* device-tree, including the platform type,initrd location and
* size, TCE reserve, and more ...
*/
of_scan_flat_dt(early_init_dt_scan_chosen,NULL);
/*Scan memory nodes and rebuild LMBs */
lmb_init();
of_scan_flat_dt(early_init_dt_scan_root,NULL);
of_scan_flat_dt(early_init_dt_scan_memory_ppc,NULL);
/*Save command line for /proc/cmdline and then parse parameters */
strlcpy(boot_command_line,cmd_line, COMMAND_LINE_SIZE);
parse_early_param();
……
of_scan_flat_dt(early_init_dt_scan_cpus,NULL);
DBG("<- early_init_devtree()\n");
}
of_scan_flat_dt函数遍历dtb每个节点依次执行传入的回调函数,直到回调函数返回非零。
early_init_dt_scan_chosen:比对是否为chosen节点,是chosen节点后,则获取其中传递的initrd-start/end的节点内容,获取成功则设置全局变量initrd_start/end,其在initramfs.c中的populate_rootfs会使用。之后获取bootargs并将其内容保存到全局变量cmd_line中,最后获取memory-limit等内存相关系数。
early_init_dt_scan_root:其根据遍历DTB的深度为0,则为root节点,获取对应的#size-cells和#address-cells参数的内容。
early_init_dt_scan_memory_ppc:获取DTB中传递的memory节点参数内容,根据获取的#size-cells和#address-cells对memory节点中的reg参数进行整合,得出最终的memory大小。
parse_early_param:对获取的cmd_line,遍历系统__setup宏声明的参数进行比对后设置该参数。
early_init_dt_scan_cpus:其主要针对于ibmpowerpc多选项功能内容的选取,在此不做细述。
接下来我们分析probe_machine:
void probe_machine(void)
{
externstruct machdep_calls __machine_desc_start;
externstruct machdep_calls __machine_desc_end;
/*
* Iterate all ppc_md structures until we findthe proper
* one for the current machine type
*/
DBG("Probingmachine type ...\n");
for(machine_id = &__machine_desc_start;
machine_id < &__machine_desc_end;
machine_id++) {
DBG(" %s ...", machine_id->name);
memcpy(&ppc_md,machine_id, sizeof(struct machdep_calls));
if(ppc_md.probe()) { /*mpc831x_rdb_probe */
DBG("match !\n");
break;
}
DBG("\n");
}
/*What can we do if we didn't find ? */
if(machine_id >= &__machine_desc_end) {
DBG("Nosuitable machine found !\n");
for(;;);
}
printk(KERN_INFO"Using %s machine description\n", ppc_md.name);
}
讲解该函数,首先了解一下
define_machine(mpc831x_rdb) {
.name = "MPC831xRDB",
.probe = mpc831x_rdb_probe,
.setup_arch = mpc831x_rdb_setup_arch,
.init_IRQ = mpc831x_rdb_init_IRQ,
.get_irq = ipic_get_irq,
.restart = mpc83xx_restart,
.time_init = mpc83xx_time_init,
.calibrate_decr = generic_calibrate_decr,
.progress = udbg_progress,
};
跟踪define_machine为:
#define __machine_desc __attribute__((__section__ (".machine.desc")))
#define define_machine(name) \
externstruct machdep_calls mach_##name; \
EXPORT_SYMBOL(mach_##name); \
structmachdep_calls mach_##name __machine_desc =
故我们知道上述定义mpc831x_rdb,则定义在.machine.desc段,则上述probe_machine中遍历__machine_desc_start到__machine_desc_end,则会获取上述定义的mach_mpc831x_rdb。由于其存在probe钩子函数,且与dtb数据匹配,则匹配成功,退出probe_machine。其后的ppc_md则为我们的machine_desc的全局变量。
调用完成machine_init后,我们回到start_here,继而接着调用到start_kernel
我们在start_kernel下主要关注:setup_arch()、
void__init setup_arch(char **cmdline_p)
{
unflatten_device_tree();
if (ppc_md.init_early)
ppc_md.init_early();
/* Register early console */
register_early_udbg_console(); /* 这一步使能调试信息输出 */
dcache_bsize =cur_cpu_spec->dcache_bsize; /*early_init中确定了cpu_spec*/
icache_bsize =cur_cpu_spec->icache_bsize;
if (ppc_md.setup_arch)
ppc_md.setup_arch();
}
unflatten_device_tree():此函数将DTB转换成device node树,方便后续的查找操作等。我们先主要分析他,之前我们先了解他的结构。
struct device_node {
constchar *name; /*device node name */
constchar *type; /*corresponding device type */
phandlephandle; /*corresponding phandle propetry */
char *full_name; /*device node absolute name */
struct property *properties; /* device node property list ptr */
struct property *deadprops; /* removed properties */
struct device_node *parent; /* parent device node ptr */
struct device_node *child; /* child device node ptr */
struct device_node *sibling; /* brother device node ptr */
struct device_node *next; /* next device of sametype */
struct device_node *allnext; /* next in list of all nodes */
struct proc_dir_entry *pde; /* this node's proc directory */
struct kref kref; /* kernel reference count */
unsignedlong _flags;
void *data;
};
该函数完成DTB的解析,完成转换到device node.
void __init unflatten_device_tree(void)
{
unsignedlong start, mem, size;
structdevice_node **allnextp = &allnodes;
pr_debug("-> unflatten_device_tree()\n");
/*First pass, scan for size */
start= ((unsigned long)initial_boot_params) +
be32_to_cpu(initial_boot_params->off_dt_struct);
size= unflatten_dt_node(0, &start, NULL, NULL, 0);
size= (size | 3) + 1;
pr_debug(" size is %lx, allocating...\n", size);
/*Allocate memory for the expanded device tree */
mem= early_init_dt_alloc_memory_arch(size + 4,
__alignof__(structdevice_node));
mem= (unsigned long) __va(mem);
((__be32*)mem)[size / 4] = cpu_to_be32(0xdeadbeef);
pr_debug(" unflattening %lx...\n", mem);
/*Second pass, do actual unflattening */
start= ((unsigned long)initial_boot_params) +
be32_to_cpu(initial_boot_params->off_dt_struct);
unflatten_dt_node(mem,&start, NULL, &allnextp, 0);
if(be32_to_cpup((__be32 *)start) != OF_DT_END)
pr_warning("Weirdtag at end of tree: %08x\n", *((u32 *)start));
if(be32_to_cpu(((__be32 *)mem)[size / 4]) != 0xdeadbeef)
pr_warning("Endof tree marker overwritten: %08x\n",
be32_to_cpu(((__be32 *)mem)[size / 4]));
*allnextp= NULL;
/*Get pointer to OF "/chosen" node for use everywhere */
of_chosen= of_find_node_by_path("/chosen");
if(of_chosen == NULL)
of_chosen= of_find_node_by_path("/chosen@0");
pr_debug("<- unflatten_device_tree()\n");
}
该函数步骤为开始调用unflaten_dt_node进行device node内存空间的计算,分配对应的内存空间(注意4字节对其),最后调用unflaten_dt_node进行真正的DTB到device node的转换,最后获取chosen节点指针,赋值给of_chosen.
unflatten_dt_node:采用迭代的方式进行flattree节点的解析。需要的可以继续跟踪下去。这里不进行细述,只需要严格参照DTB的结构即可。
之后setup_arch则会执行ppc_md.setup_arch(),即mpc831x_rdb_setup_arch.
mpc831x_rdb_setup_arch完成pci桥的IO,MMIO,MEM资料的映射,及USB的管脚时钟配置和资源映射。具体此处不细述。
我们接着回到start_kernel,接下来我们依次需要关注的函数为:
init_IRQ:此处涉及IPIC中断子系统,会有另外一篇文档分析,此处略过
init_timers()、hrtimers_init()、softirq_init()、timekeeping_init()、time_init()都涉及linux时钟子系统,此处设计内容过多,诸如timekeeping,clock_resources,softirqd,高精度定时器,低精度定时器等,不在此进行细述。
此处仅分析下mpc8313e的系统时钟初始化过程:
timekeeping_init->read_persistent_clock->__read_persistent_clock->ppc_md.time_init。
其中ppc_md.time_init仅在头一次调用__read_persistent_clock调用。
ppc_md.time_init即mpc83xx_time_init:其仅在systempriority and configuration register (SPCR),时能TBEN,使能Time Base 提交请求到coherentsystem bus。
TimeBase系统定时器的初始化设置在time_init中设置。
下面对device node中各节点的处理进行总结。
1、 cpu node,在head_32.S->early_init中对cpu特性进行处理,对于cpu节点的通用属性在cpu_table中已然映射好,在后面的early_init_devtree->early_init_dt_scan_cpus中对cpu的其他属性进行识别并启动(fixup),但是对于我们的mpc83xx没有其他特性,其主要针对于ibm的ppc。
2、 memory node,在early_init_devtree->early_init_dt_scan_root中获取#size-cells和#address-cells后,再调用early_init_devtree->early_init_dt_scan_memory_ppc中对memory node的reg属性进行对应叠加分析后,整合出系统memory信息。
3、 interrupt node,此处只对调用过程进行简略分析,其在start_kernel->init_IRQ->mpc831x_rdb_init_IRQ->ipic_init进行中断初始化,细节部分待另一文档解析。
下面细述对于device node如何并入到linux驱动模型
我们由machine_device_initcall(mpc831x_rdb, declare_of_platform_devices); 跟踪machine_device_initcall得到
#define __define_machine_initcall(mach,level,fn,id)\
staticint __init __machine_initcall_##mach##_##fn(void) { \
if(machine_is(mach)) return fn(); \
return0; \
}\
__define_initcall(level,__machine_initcall_##mach##_##fn,id);
__define_machine_initcall(mach,"6",fn,6);
调用流程为start_kernel->rest_init->kernel_init->do_basic_setup->do_initcalls->do_one_initcall
我们知道了其调用过程,则我们继续分析declare_of_platform_devices:
declare_of_platform_devices即of_platform_bus_probe外裹函数。
of_platform_bus_probe:其逻辑为先找到根节点,之后依次遍历device node,并调用of_platform_bus_create和of_platform_device_create处理。
struct of_device*of_platform_device_create(struct device_node *np,
const char *bus_id,
struct device *parent)
{
structof_device *dev;
dev= of_device_alloc(np, bus_id, parent);
if(!dev)
returnNULL;
dev->dma_mask= 0xffffffffUL;
dev->dev.coherent_dma_mask= DMA_BIT_MASK(32);
dev->dev.bus= &of_platform_bus_type;
/*We do not fill the DMA ops for platform devices by default.
* This is currently the responsibility of theplatform code
* to do such, possibly using a device notifier
*/
if(of_device_register(dev) != 0) {
of_device_free(dev);
returnNULL;
}
returndev;
}
int of_device_register(struct of_device*ofdev)
{
BUG_ON(ofdev->node== NULL);
device_initialize(&ofdev->dev);
/*device_add will assume that this device is on the same node as
* the parent. If there is no parent defined,set the node
* explicitly */
if(!ofdev->dev.parent)
set_dev_node(&ofdev->dev,of_node_to_nid(ofdev->node));
returndevice_add(&ofdev->dev);
}
上面两个函数较为简单,我们则知道of_platform_device_create即将device node注册到驱动模型上作为device设备,且其通过总线为of_platform_bus_type。
static int of_platform_bus_create(conststruct device_node *bus,
const struct of_device_id *matches,
struct device *parent)
{
structdevice_node *child;
structof_device *dev;
intrc = 0;
for_each_child_of_node(bus,child) {
pr_debug(" create child: %s\n",child->full_name);
dev= of_platform_device_create(child, NULL, parent);
if(dev == NULL)
rc= -ENOMEM;
elseif (!of_match_node(matches, child))
continue;
if(rc == 0) {
pr_debug(" and sub busses\n");
rc= of_platform_bus_create(child, matches, &dev->dev);
}if (rc) {
of_node_put(child);
break;
}
}
returnrc;
}
该函数则将dvice node注册为驱动模型中的bus节点,并将该总线下的device node依次注册到驱动模型中,依次遍历下去。即可知道从根节点开始执行,则所有的device node都会注册到of_platform总线上。
我们再看of_platform_bus_probe:
int of_platform_bus_probe(structdevice_node *root,
const struct of_device_id *matches,
struct device *parent)
{
structdevice_node *child;
structof_device *dev;
intrc = 0;
if(matches == NULL)
matches= of_default_bus_ids;
if(matches == OF_NO_DEEP_PROBE)
return-EINVAL;
if(root == NULL)
root= of_find_node_by_path("/");
else
of_node_get(root); /* kref plus ++ */
pr_debug("of_platform_bus_probe()\n");
pr_debug("starting at: %s\n", root->full_name);
/*Do a self check of bus type, if there's a match, create
* children
*/
if(of_match_node(matches, root)) { /*through the root device node, to search the simple-bus compatile device */
pr_debug("root match, create all sub devices\n");
dev= of_platform_device_create(root, NULL, parent);
if(dev == NULL) {
rc= -ENOMEM;
gotobail;
}
pr_debug("create all sub busses\n");
rc= of_platform_bus_create(root, matches, &dev->dev);
gotobail;
}
for_each_child_of_node(root,child) {
if(!of_match_node(matches, child))
continue;
pr_debug(" match: %s\n", child->full_name);
dev= of_platform_device_create(child, NULL, parent);
if(dev == NULL)
rc= -ENOMEM;
else
rc= of_platform_bus_create(child, matches, &dev->dev);
if(rc) {
of_node_put(child);
break;
}
}
bail:
of_node_put(root);
returnrc;
}
则能发现由NULL作为root参数传入的时候,则会先找到root根节点,之后依次遍历注册device node到of_platform总线上,则跳出到bail,释放root根节点后退出。
注册到of_platform总线时,通过最后的device_add调用后,则其会在of_platform总线上查找设备对应的驱动,如果存在在跳到驱动的probe钩子函数,进而使系统识别该设备。具体的过程是由驱动模型进行主导,在此不细述。