PowerPC DevTree

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 nodebootargsinitrd等属性的value,并将其保存在全局变量(boot_command_lineinitrd_startinitrd_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钩子函数,进而使系统识别该设备。具体的过程是由驱动模型进行主导,在此不细述。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值