简析PPC的Device Tree机制

年底完成了公司设备从arm到ppc的移植,有很多心得需要总结,趁年后不是很忙,整理写下来。

自己也是第一次接触ppc架构的kernel(版本号:3.4.55),很多东西学习不够深入,只写个思路框架,不去深究细节,错误地方还望大家指正。

今天首先来总结下PPC的Device Tree设备树机制,之前在移植arm的uboot以及kernel时,uboot和kernel之前的传参机制在arm架构下是可以选择的,使用tags方式还是fdt方式(flattened device tree)。我选择使用tags,之前有总结过tags的传参方式,可以参考我的另一篇文章,链接如下:

http://blog.csdn.net/skyflying2012/article/details/35787971

但是阅读了PPC架构的kernel启动代码后,发现PPC架构kernel启动传参仅支持fdt方式,趁这个机会学习下fdt机制。

1 为什么要用FDT,FDT优点是什么。

从网上找到的官方解释如下:

IBM、Sun等厂家的服务器最初都采用了Firmware(一种嵌入到硬件设备中的程序,用于提供软件和硬件之间的接口),用于初始化系统配置,提供操作系统软件和硬件之间的接口,启动和运行系统。后来为了标准化和兼容性,IBM、Sun等联合推出了固件接口IEEE 1275标准,让他们的服务器如IBM PowerPCpSeries,Apple PowerPC,Sun SPARC等均采用Open Firmware,在运行时构建系统硬件的设备树信息传递给内核,进行系统的启动运行。这样做的好处有,减少内核对系统硬件的严重依赖,利于加速支持包的开发,降低硬件带来的变化需求和成本,降低对内核设计和编译的要求。

在嵌入式PowerPC中,一般使用U-Boot之类的系统引导代码,而不采用Open Firmware。早期的U-Boot使用include/asm-ppc/u-boot.h中的静态数据结构struct bd_t将板子基本信息传递给内核,其余的由内核处理。这样的接口不够灵活,硬件发生变化就需要重新定制编译烧写引导代码和内核,而且也不再适应于现在的内核。为了适应内核的发展及嵌入式PowerPC平台的千变万化,吸收标准OpenFirmware的优点,UBoot引入了扁平设备树FDT这样的动态接口,使用一个单独的FDT blob(二进制大对象,是一个可以存储二进制文件的容器)存储传递给内核的参数,一些确定信息,例如cache大小、中断路由等直接由设备树提供,而其他的信息,例如eTSEC的MAC地址、频率、PCI总线数目等由U-Boot在运行时修改。

我的理解是为了适应灵活的嵌入式平台,FDT将一些固定人为需要修改的参数信息从uboot和kernel中(如uboot下的bd_t)剥离出来,修改硬件后,不需要重新修改烧录uboot kernel,仅需要修改FDT文件即可完成对新硬件的支持。但是有一些动态修改的信息还是需要uboot以及kernel来操作,如cmdline,usb以及pci的枚举设备信息。

对比而言,arm下使用的tags方式就是需要对uboot中的tags(如mem大小等)进行修改,完成对新硬件的支持。

2 FDT怎么用,格式是什么。

FDT设备树我们可以看做是描述设备硬件配置的线性树形数据结构,开发人员需要根据设备硬件配置来编写设备树,设备树的编写提供一套完全可视化的文本形式dts(device tree source),然后利用dtc(device tree compiler)编译成kernel需要的设备数镜像文件dtb,d t c 编译器会对输入文件进行语法和语义检查,并根据L i n u x 内核的要求检查各节点及属性,将设备树源码文件(. d t s )编译二进制文件(. d t b ),以保证内核能正常启动,一个简单的例子如下:

/ {
    #address-cells = <1>;
    #size-cells = <1>;
    model = "test";
    compatible = "test";
    dcr-parent = <&{/cpus/[email protected]0}>;

    cpus {
        #address-cells = <1>;
        #size-cells = <0>;

        [email protected]0 {
            device_type = "cpu";
            model = "PowerPC,460EX";
            reg = <0x00000000>;
            i-cache-line-size = <32>;
            d-cache-line-size = <32>;
            i-cache-size = <32768>;
            d-cache-size = <32768>;
            dcr-controller;
            dcr-access-method = "native";
        };
    };

    memory {
        device_type = "memory";
        reg = <0x80000000 0x40000000>;
    };

    chosen {
        name = "chosen";
        bootargs = "console=ttyS0,115200 mem=512M rdinit=/sbin/init";
    };
};

这是我移植kernel时根据kernel下提供的dts文件修改的,kernel下已经有很多设备的dts文件,在arch/powerpc/boot/dts下,并且也集成了dtc编译器,我上面的dts文件是arch/powerpc/boot/dts/test.dts,则我可以在kernel下运行如下命令:

make test.dtb

就可以生成对应的dtb镜像。

对于开发人员来说,直接面对的是dts文件,下来就来说下dts文件的格式:

(dts格式网上有很多详细解释,并且在kernel下也有详细说明的文档,是Documentation/devicetree/booting-without-of.txt)

1 根节点

设备树的起始点称之为根节点” / ” 。属性m o d e l 指明了目标板平台或模块的名称,属性c o m p a t i b l e 值指明和目标板为同一系列的兼容的开发板名称。对于大多数3 2 位平台,属性# a d d r e s s - c e l l s 和# s i z e - c e l l s 的值一般为1 ,address-cells和size-cells分别定义了子节点地址和长度的宽度。

2 CPU节点

/ c p u s 节点是根节点的子节点,对于系统中的每一个C P U ,都有相应的节点。/ c p u s 节点没有必须指明的属性,但指明# a d d r e s s - c e l l s = < 1 > 和 # s i z e - c e l l s = < 0 > 是个好习惯,这同时指明了每个C P U 节点的r e g 属性格式,方便为物理C P U 编号。C P U 节点的单元名应该是c p u @ 0 的格式,此节点一般要指定d e v i c e _ t y p e (固定为” c p u ” ),一级数据/ 指令缓存的表项大小,一级数据/ 指令缓存的大小,核心、总线时钟频率等。在上面的示例中通过系统引导代码动态填写时钟频率相关项。

3 系统内存节点

此节点用于描述目标板上物理内存范围,一般称作/ m e m o r y 节点,可以有一个或多个。当有多个节点时,需要后跟单元地址予以区分;只有一个单元地址时,可以不写单元地址,默认为0 。

此节点包含板上物理内存的属性,一般要指定d e v i c e _ t y p e (固定为” m e m o r y ” )和r e g 属性。其中r e g 的属性值以< 起始地址 空间大小> 的形式给出,如上示例中目标板内存起始地址为0x80000000 ,大小为1G字节。

4 /chosen节点

这个节点有一点特殊。通常,这里由O p e n F i r m w a r e 存放可变的环境信息,例如参数,默认输入输出设备。

这个节点中一般指定b o o t a r g s 及l i n u x , s t d o u t - p a t h 属性值。b o o t a r g s 属性设置为传递给内核命令行的参数字符串。l i n u x , s t d o u t - p a t h 常常为标准终端设备的节点路径名,内核会以此作为默认终端。U - B o o t 在1 . 3 . 0 版本后添加了对扁平设备树F D T 的支持,U - B o o t 加载L i n u x 内核、R a m d i s k 文件系统(如果使用的话)和设备树二进制镜像到物理内存之后,在启动执行L i n u x 内核之前,它会修改设备树二进制文件。它会填充必要的信息到设备树中,例如M A C 地址、P C I 总线数目等。U - B o o t 也会填写设备树文件中的“/ c h o s e n ”节点,包含了诸如串口、根设备(R a m d i s k 、硬盘或N F S 启动)等相关信息。U - B o o t 源码c o m m o n / c m d _ b o o t m . c 的如下代码,显示了在执行内核代码前将调用f t _ s e t u p 函数填写设备树。

dts中最多的是SOC上的外设硬件配置,因为我在移植中为了保证原来原先依赖于arm框架的代码不变(没有使用FDT),模块driver中尽量不用设备树,所以dts中没有写外设硬件配置,这个有时间再去仔细研究。

3 kernel如何解析FDT

现在学习代码,已经不像刚毕业那会对于任何代码都会死抠细节,而是想观其大略,了解其框架,待需要细究时在仔细研究,我想这也是一种进步,能让自己在kernel星辰大海中更加从容一点。

学习代码,我一直追求弄明白原因(为什么这样做)和方法(如何做)。

首先来看dtc编译dts生成的dtb镜像文件是什么格式的。

1 设备树主要由三大部分组成:头(H e a d e r )、结构块(S t r u c t u r e b l o c k )、字符串块(S t r i n g s b l o c k )。在内存中分配图如下:

头主要描述设备树的基本信息,如设备树魔数标志、设备树块大小、结构块的偏移地址等,其具体结构b o o t _ p a r a m _ h e a d e r 如下。这个结构中的值都是以大端模式表示,并且偏移地址是相对于设备树头的起始地址计算的。

/*
 * This is what gets passed to the kernel by prom_init or kexec
 *
 * The dt struct contains the device tree structure, full pathes and
 * property contents. The dt strings contain a separate block with just
 * the strings for the property names, and is fully page aligned and
 * self contained in a page, so that it can be kept around by the kernel,
 * each property name appears only once in this page (cheap compression)
 *
 * the mem_rsvmap contains a map of reserved ranges of physical memory,
 * passing it here instead of in the device-tree itself greatly simplifies
 * the job of everybody. It‘s just a list of u64 pairs (base/size) that
 * ends when size is 0
 */
struct boot_param_header {
    __be32  magic;          /* magic word OF_DT_HEADER */
    __be32  totalsize;      /* total size of DT block */
    __be32  off_dt_struct;      /* offset to structure */
    __be32  off_dt_strings;     /* offset to strings */
    __be32  off_mem_rsvmap;     /* offset to memory reserve map */
    __be32  version;        /* format version */
    __be32  last_comp_version;  /* last compatible version */
    /* version 2 fields below */
    __be32  boot_cpuid_phys;    /* Physical CPU id we‘re booting on */
    /* version 3 fields below */
    __be32  dt_strings_size;    /* size of the DT strings block */
    /* version 17 fields below */
    __be32  dt_struct_size;     /* size of the DT structure block */
};

2 结构块(structure block)

扁平设备树结构块是线性化的树形结构,和字符串块一起组成了设备树的主体,以节点形式保存目标板的

设备信息。在结构块中,节点起始标志为3 2 位常值宏O F _ D T _ B E G I N _ N O D E ,节点结束标志为宏O F _ D T _ E N D _ N O D E ;子节点定义在节点结束标志前。一个节点的基本结构如下所示:

1 . 节点起始标志O F _ D T _ B E G I N _ N O D E (即0 x 0 0 0 0 _ 0 0 0 1 );

2 . 节点路径或者节点单元名(v e r s i o n < 3 以及节点路径表示,v e r s i o n > 1 6 时以节点单元名表示);

3 . 填充字节保证四字节对齐;

4 . 节点属性。每个属性以常值宏O F _ D T _ P R O P 开始,后面依次为属性值的字节长度、属性名在在字符串块

中的偏移值、属性值及字节对齐填充段;

5 . 如果存在子节点,则定义子节点。

6 . 节点结束标志O F _ D T _ E N D _ N O D E (即0 x 0 0 0 0 _ 0 0 0 2 )。

归纳起来,一个节点可以概括为以O F _ D T _ B E G I N _ N O D E 开始,节点路径、属性列表、子节点列表以及

O F _ D T _ E N D _ N O D E 结束的序列,每一个子节点自身也是类似的结构。

3 字符串块(Strings block)

为了节省空间,对于那些属性名,尤其是很多属性名是重复冗余出现的,提取出来单独存放到字符串块。

这个块中包含了很多有结束标志的属性名字符串。在设备树的结构块中存储了这些字符串的偏移地址,因

为可以很容易的查找到属性名字符串。字符串块的引入节省嵌入式系统较为紧张的存储空间。

4 kernel如何解析FDT

我们利用dtc编译了dts文件生成dtb,那么kernel就会“反汇编”dtb,从而获取其中的配置信息,因此上面描述到的dtb文件存储格式都会在kernel的解析中体现出来。

dtb文件是独立于bootloader以及kernel存在的,dtb中的chosen节点需要uboot中进行填写,dtb镜像地址也由uboot传递给kernel,保存在r3寄存器中,但是由于我移植中dtb的chosen手动填写,并且不用uboot启动kernel,所以修改kernel启动代码,直接写死dtb的首地址,代码如下:

/* As with the other PowerPC ports, it is expected that when code
 * execution begins here, the following registers contain valid, yet
 * optional, information:
 *
 *   r3 - Board info structure pointer (DRAM, frequency, MAC address, etc.)
 *   r4 - Starting address of the init RAM disk
 *   r5 - Ending address of the init RAM disk
 *   r6 - Start of kernel command line string (e.g. "mem=128")
 *   r7 - End of kernel command line string
 *
 */
    __HEAD
_ENTRY(_stext);
_ENTRY(_start);
    /*
     * Reserve a word at a fixed location to store the address
     * of abatron_pteptrs
     */
    nop

    #device tree phy addr
    lis r3, 0x81000000@h
    ori r3, r3, 0x81000000@l

    mr  r31,r3      /* save device tree ptr */
    li  r24,0       /* CPU number */

PPC架构kernel对FDT解析可以分为两部分:

第一步是早期解析,获取kernel启动必需的cmdline以及cpu mem等信息。

第二步是后期的完全解析,以供driver加载时获取对应配置信息使用。

由于移植中尽量让driver不使用FDT,所以今天主要分析早期解析过程,进入start kernel之前调用machine init

在arch/powerpc/kernel/setup_32.c中,machine init则调用early init devtree完成早期设备树的解析,在arch/powerpc/kernel/prom.c,代码如下:

void __init early_init_devtree(void *params)
{
    phys_addr_t limit;

    /* Setup flat device-tree pointer */
    initial_boot_params = params;

#ifdef CONFIG_PPC_RTAS
    /* Some machines might need RTAS info for debugging, grab it now. */
    of_scan_flat_dt(early_init_dt_scan_rtas, NULL);
#endif

#ifdef CONFIG_PPC_POWERNV
    /* Some machines might need OPAL info for debugging, grab it now. */
    of_scan_flat_dt(early_init_dt_scan_opal, NULL);
#endif

#ifdef CONFIG_FA_DUMP
    /* scan tree to see if dump is active during last boot */
    of_scan_flat_dt(early_init_dt_scan_fw_dump, NULL);
#endif

    /* Pre-initialize the cmd_line with the content of boot_commmand_line,
     * which will be empty except when the content of the variable has
     * been overriden by a bootloading mechanism. This happens typically
     * with HAL takeover
     */
    strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);

    /* 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_ppc, cmd_line);

    /* Scan memory nodes and rebuild MEMBLOCKs */
    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();

    /* make sure we‘ve parsed cmdline for mem= before this */
    if (memory_limit)
        first_memblock_size = min(first_memblock_size, memory_limit);
    setup_initial_memory_limit(memstart_addr, first_memblock_size);
    /* Reserve MEMBLOCK regions used by kernel, initrd, dt, etc... */
    memblock_reserve(PHYSICAL_START, __pa(klimit) - PHYSICAL_START);
    /* If relocatable, reserve first 32k for interrupt vectors etc. */
    if (PHYSICAL_START > MEMORY_START)
        memblock_reserve(MEMORY_START, 0x8000);
    reserve_kdump_trampoline();
#ifdef CONFIG_FA_DUMP
    /*
     * If we fail to reserve memory for firmware-assisted dump then
     * fallback to kexec based kdump.
     */
    if (fadump_reserve_mem() == 0)
#endif
        reserve_crashkernel();
    early_reserve_mem();

    /*
     * Ensure that total memory size is page-aligned, because otherwise
     * mark_bootmem() gets upset.
     */
    limit = ALIGN(memory_limit ?: memblock_phys_mem_size(), PAGE_SIZE);
    memblock_enforce_memory_limit(limit);

    memblock_allow_resize();
    memblock_dump_all();

    DBG("Phys. mem: %llx\n", memblock_phys_mem_size());

    /* We may need to relocate the flat tree, do it now.
     * FIXME .. and the initrd too? */
    move_device_tree();

    allocate_pacas();

    DBG("Scanning CPUs ...\n");

    /* Retrieve CPU related informations from the flat tree
     * (altivec support, boot CPU ID, ...)
     */
    of_scan_flat_dt(early_init_dt_scan_cpus, NULL);

#if defined(CONFIG_SMP) && defined(CONFIG_PPC64)
    /* We‘ll later wait for secondaries to check in; there are
     * NCPUS-1 non-boot CPUs  :-)
     */
    spinning_secondaries = boot_cpu_count - 1;
#endif

    DBG(" <- early_init_devtree()\n");
}

调用of_scan_flat_dt来遍历dtb中所有节点,调用解析函数early_init_dt_scan_chosen_ppc early_init_dt_scan_mem_ppc early_init_dt_scan_root early_init_dt_scan_cpus,分别获取chosen mem cpus节点信息,完成早期cmdline mem cpu的操作。我们来看一个mem的解析函数,代码如下:

int __init early_init_dt_scan_chosen(unsigned long node, const char *uname,
                     int depth, void *data)
{
    unsigned long l;
    char *p;

    pr_debug("search \"chosen\", depth: %d, uname: %s\n", depth, uname);

    if (depth != 1 || !data ||
        (strcmp(uname, "chosen") != 0 && strcmp(uname, "[email protected]") != 0))
        return 0;

    early_init_dt_check_for_initrd(node);

    /* Retrieve command line */
    p = of_get_flat_dt_prop(node, "bootargs", &l);
    if (p != NULL && l > 0)
        strlcpy(data, p, min((int)l, COMMAND_LINE_SIZE));

    /*
     * CONFIG_CMDLINE is meant to be a default in case nothing else
     * managed to set the command line, unless CONFIG_CMDLINE_FORCE
     * is set in which case we override whatever was found earlier.
     */
#ifdef CONFIG_CMDLINE
#ifndef CONFIG_CMDLINE_FORCE
    if (!((char *)data)[0])
#endif
        strlcpy(data, CONFIG_CMDLINE, COMMAND_LINE_SIZE);
#endif /* CONFIG_CMDLINE */

    pr_debug("Command line is: %s\n", (char*)data);

    /* break now */
    return 1;
}

对于fdt的处理函数主要在arch/powerpc/kernel/prom.c以及driver/of/fdt.c中。

与之前文章分析tags解析方式对比,可以看出FDT的解析跟tags解析的差别之处在于,

tags是采用注册回调函数方式,解析什么类型tags,则调用该类型对应处理函数。

fdt是采用遍历整个设备树,在处理函数中判断是否是所需要解析的内容,然后进行处理。

时间: 2024-08-03 21:20:35

简析PPC的Device Tree机制的相关文章

Device Tree(一):背景介绍

原文网址:http://www.wowotech.net/device_model/why-dt.html 一.前言 作为一个多年耕耘在linux 2.6.23内核的开发者,各个不同项目中各种不同周边外设驱动的开发以及各种琐碎的.扯皮的俗务占据了大部分的时间.当有机会下载3.14的内核并准备学习的时候,突然发现linux kernel对于我似乎变得非常的陌生了,各种新的机制,各种framework.各种新的概念让我感到阅读内核代码变得举步维艰. 还好,剖析内核的热情还在,剩下的就交给时间的.首先

Android WebView远程代码执行漏洞简析

0x00 本文参考Android WebView 远程代码执行漏洞简析.代码地址为,https://github.com/jltxgcy/AppVulnerability/tree/master/WebViewFileDemo.下面我们分析代码. 0x01 首先列出项目工程目录: MainActivity.java的代码如下: public class MainActivity extends Activity { private WebView webView; private Uri mUr

借助LANMT构架,简析ngnix的使用

LNMP流程图 nginx PHP Mysql Nginx Fastcgi_pass <-FastCGI-> fastcgi-(php-fpm)<->wrapper Php 解析器 (Php.ini) <->mysql fastcgi-(php-fpm)<->wrapper fastcgi-(php-fpm)<->wrapper fastcgi-(php-fpm)<->wrapper fastcgi-(php-fpm)<->

CentOS的网络配置简析

我们在进行对CentOS的网络配置时,一般会从IP地址(IPADDR).子网掩码(NETMASK).网关(Gateway).主机名(HOSTNAME).DNS服务器等方面入手.而在CentOS中,又有着不同的命令或配置文件可以完成这些配置操作,接下来,我们将从ifcfg系命令,iproute2系命令以及配置文件3个方面来简析网络配置的方法. 一.ifcfg系命令 ifcfg系命令包括ifconfig,route,netstat和hostname. 1.ifconfig命令 用来配置一个网络接口.

[转载] Thrift原理简析(JAVA)

转载自http://shift-alt-ctrl.iteye.com/blog/1987416 Apache Thrift是一个跨语言的服务框架,本质上为RPC,同时具有序列化.发序列化机制:当我们开发的service需要开放出去的时候,就会遇到跨语言调用的问题,JAVA语言开发了一个UserService用来提供获取用户信息的服务,如果服务消费端有PHP/Python/C++等,我们不可能为所有的语言都适配出相应的调用方式,有时候我们会很无奈的使用Http来作为访问协议;但是如果服务消费端不能

The Linux usage model for device tree data

Linux and the Device Tree Author: Grant Likely [email protected] 这篇文章介绍了Linux中使用Device Tree的方法.可以在http://devicetree.org/Device_Tree_Usage获取到Device Tree数据格式. Device Tree是一种描述硬件的语言,它可以让操作系统不硬编码硬件的信息. 结构上讲,Device Tree是树形结构,或者非循环的有名节点组成的图.每个节点包含一定数目的属性和键

TCP,UDP,IP 协议简析

现在的操作系统基本都实现了TCP/IP协议,TCP/IP协议栈分为五层: 应用层:向用户提供的一组常用的应用程序,如TELNET,FTP,SMTP,SNTP,DNS,HTTP,这些应用程序有一个端口用来标识. 传输层:主要协议是TCP和UDP,提供应用程序的通信. 网络层:主要协议是IP协议,定义了IP地址格式,是不同应用程序的数据在网络上通畅传输的关键. 链路层:这是TCP/IP软件的最低层,负责接收IP数据包并通过网络发送之,或者从网络上接收物理帧,抽出IP数据报,交给IP层. 物理层: 每

Device Tree(二):基本概念

转自:http://www.wowotech.net/linux_kenrel/dt_basic_concept.html 一.前言 一些背景知识(例如:为何要引入Device Tree,这个机制是用来解决什么问题的)请参考引入Device Tree的原因,本文主要是介绍Device Tree的基础概念. 简单的说,如果要使用Device Tree,首先用户要了解自己的硬件配置和系统运行参数,并把这些信息组织成Device Tree source file.通过DTC(Device Tree C

cgroup原理简析:进程调度

本篇来探究下cgroup对cpu的限制机制,前文提到过cgroup也是通过进程调度子系统来达到限制cpu的目的,因此需要了解下进程调度子系统. 因为是介绍cgroup的文章,因此只介绍进程调度中与cgroup密切关联的部分,详细完成的进程调度实现可参考进程调度的相关资料. 本文分为三个部分,首先介绍进程调度中的调度算法,在该基础上引入组调度,最后结合前面文章(cgroup原理简析:vfs文件系统)来说明上层通过echo pid >> tasks, echo n > cpu.shares等