【嵌入式Linux学习七步曲之第三篇 Linux系统bootlaoder移植】全面解析PowerPC架构下的扁平设备树FDT

全面解析PowerPC架构下的扁平设备树FDT


Sailor_forever  sailing_9806#163.com

(本原创文章发表于Sailor_forever 的个人blog,未经本人许可,不得用于商业用途。任何个人、媒体、其他网站不得私自抄袭;网络媒体转载请注明出处,增加原文链接,否则属于侵权行为。如有任何问题,请留言或者发邮件给sailing_9806@163.com)
http://blog.csdn.net/sailor_8318/archive/2009/12/26/5078959.aspx



【摘要】本文以MPC8378处理器、Linux2.6.25内核及U-boot1.3.4为例,讲述了如何在PowerPC架构下使用FDT。首先介绍了引入FDT的背景,接着详细介绍了FDT的组成及制作。最后介绍了U-boot及内核如何支持FDT。


【关键字】PowerPC,MPC8378,DTS,DTB,FDT,device node,property,compatible


1    背景    2
2    设备树的描述方式    2
2.1    root Node    3
2.2    chosen    3
2.3    cpus Node    3
2.4    System Memory    5
2.5    Devices    5
2.5.1    Compatible属性    6
2.5.2    Addressing    6
2.6    Interrupts and Interrupt Controllers    7
3    如何制作设备树映像    8
3.1    输入    8
3.2    输出    9
3.3    命令格式    9
4    设备树的传递途径    9
4.1    U-boot对FDT的支持    10
4.2    如何配置FDT    10
4.3    如何传递设备树    10
5    内核如何解析设备树    12
6    设备树对驱动设计产生的影响    15



1    背景
通常情况下,桌面机和服务器可以兼容大部分软件。最好的结果就是当添加新硬件时,无需重新编译Linux内核。标准的固件接口可以保证bootloader将正确的参数传递给内核。PC可以采用bios,PowerPC and Sparc采用Open-Firmware接口。但是对于嵌入式系统,软件差别太大,内核本身也是定制的,bootloader只需要传递很少的参数,因为大部分信息都是硬编码在系统的配置中的。这样同一个内核映像很难同时使用在多个不同的平台上。

早期的PowerPC平台采用特定的数据结构bd_info来传递参数,其定义在include/asm-ppc/ppcboot.h,#define来定义特定平台的数据域,但并没有什么信息来说明当前采用的是那个bd_info结构,所以必须保证在内核和bootloader中同时更新,以便保持一致。

合并32-bit (arch/ppc) 和 64-bit (arch/ppc64) PowerPC的同时,决定重新整理固件接口,建立新的目录arch/powerpc,这里所有的平台必须向内核提供Open Firmware风格的设备树,以便内核启动时可以获得当前平台的硬件配置。

2    设备树的描述方式
简单的说设备书是一种描述硬件配置信息的数据结构,包括CPU,内存,总线及相关外设。内核启动时可以解析这些信息,以此决定如何配置内核及加载那些驱动。该数据结构有一个单一的根节点“/”。每个节点有个名字并可以包含多个子节点。数据的格式遵循IEEE standard 1275。

Device tree source (.dts)采用一种易编辑的文本方式来表达设备树,device tree compiler tool (dtc)将.dts转换成binary device tree blob(.dtb)。设备树并不是控制系统设备的唯一方法,比如内核对USB和PCI已经有非常方便的检测机制。

/ { // the root node
    an-empty-property;
   
    a-child-node {
    array-prop = <0x100 32>;
    string-prop = "hello, world";
    };
   
    another-child-node {
    binary-prop = [0102CAFE];
    string-list = "yes","no","maybe";
    };
};
Figure 1: Simple example of the .dts file format

2.1    root Node
设备树的起点是根节点,Model和compatible属性指明了当前平台的名字,格式为<mfg>,<board>:
Mfg是vendor,board是板子模型

Compatible属性不一定非得要,但是当两个系统在硬件配置上基本一致时,这个参数可以用于辨别当前系统。
/ {
    model = "fsl,mpc8377rdb";
    compatible = "fsl,mpc8377rdb";
    #address-cells = <1>;
    #size-cells = <1>;

    aliases {
        ethernet0 = &enet0;
        ethernet1 = &enet1;
        serial0 = &serial0;
        serial1 = &serial1;
        //pci0 = &pci0;
    };
    // Child nodes go here
};
Figure 3: Example system root node

2.2    chosen
此节点并不真正代表设备节点,而是一些虚拟的由bootloader传递给内核的一些参数,包括bootargs(cmdline)和initrd等。一般由bootloader在启动内核时添加此节点。

2.3    cpus Node
cpus节点是root节点的子节点,对于多核CPU系统,每个CPU有一个子节点。Cpus节点并不需要特别的特性,但是通常习惯指定#address-cells = <1>和#size-cells = <0>,这指定了各个CPU节点的reg属性的格式,其用于编码物理CPU号。

CPU节点的格式为cpu@x,model属性描述CPU类型,其他的是时钟频率及cache 相关属性。
cpus {
    #cpus = <1>;
    #address-cells = <1>;
    #size-cells = <0>;

    PowerPC,8377@0 {
        device_type = "cpu";
        model = "PowerPC, 8377";
        reg = <0x0>;
        d-cache-line-size = <32>;
        i-cache-line-size = <32>;
        d-cache-size = <32768>;
        i-cache-size = <32768>;
        timebase-frequency = <0>;
        bus-frequency = <0>;
        clock-frequency = <0>;
    };
};
Figure 4: cpus node

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

    PowerPC,8641@0 {
        device_type = "cpu";
        reg = <0>;
        d-cache-line-size = <20>;    // 32 bytes
        i-cache-line-size = <20>;    // 32 bytes
        d-cache-size = <8000>;        // L1, 32K
        i-cache-size = <8000>;        // L1, 32K
        timebase-frequency = <0>;    // 33 MHz, from uboot
        bus-frequency = <0>;        // From uboot
        clock-frequency = <0>;        // From uboot
        32-bit;
        linux,boot-cpu;
    };
    PowerPC,8641@1 {
        device_type = "cpu";
        reg = <1>;
        d-cache-line-size = <20>;    // 32 bytes
        i-cache-line-size = <20>;    // 32 bytes
        d-cache-size = <8000>;        // L1, 32K
        i-cache-size = <8000>;        // L1, 32K
        timebase-frequency = <0>;    // 33 MHz, from uboot
        bus-frequency = <0>;        // From uboot
        clock-frequency = <0>;        // From uboot
        32-bit;
    };
};
2.4    System Memory
描述系统内存的节点成为memory node,其为root节点的子节点,通常只用一个memory节点描述系统所有的内存范围,reg属性用来定义当前可用的各个memory范围。
memory {
    device_type = "memory";
    reg = <0x00000000 0x40000000>;    // 256MB at 0
};
Figure 5: Memory node

2.5    Devices
一系列节点用于描述系统总线及设备,每个总线及设备在设备树种都有自己的节点。处理器的local bus通常直接作为根节点的子节点,附着在local bus上的Devices and bridges将作为其子节点。
下图显示的PLB bus上的设备包括interrupt controller, an Ethernet device,
及 OPB bridge,OPB总线上有serial devices and a Flash device
plb {
    compatible = "simple-bus";
    #address-cells = <1>;
    #size-cells = <1>;
    ranges;
    UIC0: interrupt-controller {
    compatible = "ibm,uic-440gp",
    "ibm,uic";
    interrupt-controller;
    #interrupt-cells = <2>;
    };
    ethernet@20000 {
    compatible = "ibm,emac-440gp";
    reg = <0x20000 0x70>;
    interrupt-parent = <&UIC0>;
    interrupts = <0 4>;
    };
    opb {
        compatible = "simple-bus";
        #address-cells = <1>;
        #size-cells = <1>;
        ranges = <0x0 0xe0000000
        0x20000000>;
        serial@0 {
        compatible = "ns16550";
        reg = <0x0 0x10>;
        interrupt-parent = <&UIC0>;
        interrupts = <1 4>;
        };
        serial@10000 {
        compatible = "ns16550";
        reg = <0x10000 0x10>;
        interrupt-parent = <&UIC0>;
        interrupts = <2 4>;
        };
        flash@1ff00000 {
        compatible = "amd,s29gl256n",
        "cfi-flash";
        reg = <0x1ff00000 0x100000>;
        };
    };
};
Figure 6: Simple System Device Hierarchy

2.5.1    Compatible属性
几乎每个设备都有compatible属性,OS利用此关键字来确定node所描述的设备,通常compatible字符串的格式如下:
<manufacturer>,<part-num>
对于每个特定的compatible值,需要为该设备定义一个device tree binding。
有时候compatible是一系列字符串,如果某个设备在寄存器级别和某个旧设备兼容,则可以同时指定多个字串,这样OS就知道这两个驱动是兼容的。通常该设备的compatible字串在前,然后是兼容的旧设备的字串。

2.5.2    Addressing
设备地址由reg属性指定,其为一系列cell单元。格式如下:
reg = <base1 size1 [base2 size2 [...]]>;
每个reg的实际大小有父节点的#address-cells and #size-cells属性决定,#address-cells是用来指定base address基地址的cells个数,#size-cells是用来指定region size的cells个数。Reg所使用的cells个数必须是(#address-cells + #size-cells)的倍数。

Reg定义的是bus address,而非system address,bus address是设备依赖的总线上的相对地址,或者更专业的说bus address是相对于父节点的。

Ranges属性可以将bus address映射到父节点一级,格式如下:
ranges = <addr1 parent1 size1 [...]>;
addr为总线地址,宽度为#address-cells,parent是父节点总线上的地址,宽度为父节点的#address-cells,size宽度为父节点的#size-cells。但是当总线地址和父节点地址映射关系为1:1时,可以简化映射关系:
ranges;

在本示例中,Flash在OPB总线上的地址为0x1ff00000,但OPB总线中,PLB bus address 0xe0000000 映射到了0x0000000 on the OPB bus,因此Flash设备的地址为0xfff00000。

2.6    Interrupts and Interrupt Controllers
设备树的自然布局很方便描述设备间的简单关系,但是中断系统是个比较复杂的例子。可以将serial device描述为OPB总线的子节点,但也可以说其是interrupt controller设备的子节点,那么如何描述呢?目前的规范是,自然树的结构适用于描述那些寻址和控制设备的主要接口,次要连接可以通过phandle属性来描述相互之间的关系,其为节点中的一个指针,指向另一个节点。

对于中断连接,设备节点利用interrupt-parent and interrupts属性来描述到interrupt controller的连接。interrupt-parent是指向描述interrupt controller节点的指针,interrupts是interrupt controller可以触发的一系列中断信号。

Interrupt controller节点必须定义空属性interrupt-controller,同时定义#interrupt-cells,确定几个cells描述一个中断信号。由于Interrupt controller节点在设备树种被其他节点链接,因此必须定义属性linux,phandle = <xx>。

对于大部分SOC系统,通常只有一个interrupt controller,,但是多个interrupt controller之间可以级联。interrupt controller和设备之间的关系就形成了interrupt tree。

对于serial device node,interrupt-parent属性定义了其在中断树中与其父节点的关系。Interrupts属性定义了特定的中断标识,其格式取决于中断树中父节点的#interrupt-cells,通常#interrupt-cells为2,这样第一个值表示interrupt controller中的硬件中断编号,第二个值表示中断触发方式:电平触发或者边沿触发。

/* IPIC
 * interrupts cell = <intr #, sense>
 * sense values match linux IORESOURCE_IRQ_* defines:
 * sense == 8: Level, low assertion
 * sense == 2: Edge, high-to-low change
 */
pic@700 {
    linux,phandle = <700>;
    interrupt-controller;
    #address-cells = <0>;
    #interrupt-cells = <2>;
    reg = <700 100>;
    built-in;
    device_type = "ipic";
};

serial@4500 {
    device_type = "serial";
    compatible = "ns16550";
    reg = <4500 100>;
    clock-frequency = <0>;
    interrupts = <9 8>;
    interrupt-parent = <700>;
};

3    如何制作设备树映像
Device tree compiler(dtc)负责将文本格式的设备树转换成OS可以识别的格式。
3.1    输入
Dtc接受三种输入格式:
源文件,即device tree source;
Blob (dtb),flattened tree format,主要用于检查现有的DTB映像;
FS文件系统,/proc/device-tree下面的文件树目录,主要用于从当前运行的内核中获得设备树映像。

-sh-3.1# ls -al /proc/device-tree
ls -l /proc/device-tree/
-r--r--r--    1 root     root            4 Jan  1 00:05 #address-cells
-r--r--r--    1 root     root            4 Jan  1 00:05 #size-cells
dr-xr-xr-x    2 root     root            0 Jan  1 00:05 aliases
dr-xr-xr-x    2 root     root            0 Jan  1 00:05 chosen
-r--r--r--    1 root     root           15 Jan  1 00:05 compatible
dr-xr-xr-x    3 root     root            0 Jan  1 00:05 cpus
dr-xr-xr-x   15 root     root            0 Jan  1 00:05 immr@e0000000
dr-xr-xr-x    4 root     root            0 Jan  1 00:05 localbus@e0005000
dr-xr-xr-x    2 root     root            0 Jan  1 00:05 memory
-r--r--r--    1 root     root            1 Jan  1 00:05 name
dr-xr-xr-x    2 root     root            0 Jan  1 00:05 pci@e000a000
dr-xr-xr-x    2 root     root            0 Jan  1 00:05 redbox-fpga-card0@F0000000
dr-xr-xr-x    2 root     root            0 Jan  1 00:05 redbox-fpga-card1@F0000000
dr-xr-xr-x    2 root     root            0 Jan  1 00:05 redbox-fpga-dbstate@F0000000
dr-xr-xr-x    2 root     root            0 Jan  1 00:05 redbox-fpga-misc@F0000000

ls -l /proc/device-tree/immr/@e0000000/
-r--r--r--    1 root     root            4 Jan  1 00:06 #address-cells
-r--r--r--    1 root     root            4 Jan  1 00:06 #size-cells
-r--r--r--    1 root     root            4 Jan  1 00:06 bus-frequency
-r--r--r--    1 root     root           11 Jan  1 00:06 compatible
-r--r--r--    1 root     root            4 Jan  1 00:06 device_type
dr-xr-xr-x    2 root     root            0 Jan  1 00:06 ethernet@24000
dr-xr-xr-x    2 root     root            0 Jan  1 00:06 ethernet@25000
dr-xr-xr-x    2 root     root            0 Jan  1 00:06 i2c@3000
dr-xr-xr-x    3 root     root            0 Jan  1 00:06 i2c@3100
dr-xr-xr-x    2 root     root            0 Jan  1 00:06 interrupt-controller@700
dr-xr-xr-x    3 root     root            0 Jan  1 00:06 mdio@24520
-r--r--r--    1 root     root            5 Jan  1 00:06 name
dr-xr-xr-x    2 root     root            0 Jan  1 00:06 power@b00
-r--r--r--    1 root     root           12 Jan  1 00:06 ranges
-r--r--r--    1 root     root            8 Jan  1 00:06 reg
dr-xr-xr-x    2 root     root            0 Jan  1 00:06 serial@4500
dr-xr-xr-x    2 root     root            0 Jan  1 00:06 serial@4600
dr-xr-xr-x    2 root     root            0 Jan  1 00:06 spi@7000
dr-xr-xr-x    2 root     root            0 Jan  1 00:06 timer@500
dr-xr-xr-x    2 root     root            0 Jan  1 00:06 usb@23000
dr-xr-xr-x    2 root     root            0 Jan  1 00:06 wdt@200

3.2    输出
Blob (dtb),主要用于从DTS获得设备树映像;
source (dts), 当输入参数为Blob (dtb),可以“反汇编”出设备树源文件;
assembler source (asm),其最终可以编译成.O文件,可以链接到bootloader中或者frrmware image。

3.3    命令格式
dtc [-I <input -format > ] [-O <output -format > ] [-o output-filename] [-V output_version] input_filename

dtc -I dts -O dtb -S 0x3000 -o obj_name.dtb source_name.dts
-S 指定的是生成的dtb文件的大小,需要适当地扩大以供u-boot 创建/choose节点时使用

4    设备树的传递途径
对于Open Firmware (OF)系统,prom_init.c中的代码负责解析设备树,并将其转化为blob印象。

对于无Open Firmware (OF)的系统,内核可以直接从入口启动,并接受外部传递的flattened device tree参数。对于嵌入式系统,此参数由bootloader提供,或者封装过的zImage映像提供。

4.1    U-boot对FDT的支持
U-boot为了支持FDT,专门添加了新的代码,如下:
/Libfdt目录
fdt.h
libfdt.h
fdt_support.h
fdt_support.c

4.2    如何配置FDT
通常在板子配置头文件定义相关宏,以支持FDT
/* Pass open firmware flat tree */
#define CONFIG_OF_LIBFDT    1
#define CONFIG_OF_BOARD_SETUP    1
#define CONFIG_OF_STDOUT_VIA_ALIAS 1

CONFIG_OF_BOARD_SETUP宏表示会对设备树中的部分参数进行调整,主要是timebase-frequency,bus-frequency,clock-frequency等参数,在设备树配置文件中,这些参数可能为0,即采用U-boot中的参数。

4.3    如何传递设备树
Lib_ppc/board.c中do_bootm_linux负责启动Linux内核。
#if defined(CONFIG_OF_LIBFDT)
#include <fdt.h>
#include <libfdt.h>
#include <fdt_support.h>

static void fdt_error (const char *msg);
static int boot_get_fdt (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],
        bootm_headers_t *images, char **of_flat_tree, ulong *of_size);
static int boot_relocate_fdt (struct lmb *lmb, ulong bootmap_base,
        cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],
        char **of_flat_tree, ulong *of_size);
#endif

相关流程如下:
 

在该流程中,主要从启动参数中找出设备树,然后在设备树中添加chosen 节点, 并将initrd 的地址地址,结束地址,bootargs,cmd_line 等参数保存到chosen 节点中,最后根据是否支持扁平设备树选择不同的内核启动方式。

#if defined(CONFIG_OF_LIBFDT)
    if (of_flat_tree) {    /* device tree; boot new style */
        /*
         * Linux Kernel Parameters (passing device tree):
         *   r3: pointer to the fdt, followed by the board info data
         *   r4: physical pointer to the kernel itself
         *   r5: NULL
         *   r6: NULL
         *   r7: NULL
         */
        debug ("   Booting using OF flat tree.../n");
        (*kernel) ((bd_t *)of_flat_tree, (ulong)kernel, 0, 0, 0);
        /* does not return */
    } else
#endif
    {
        /*
         * Linux Kernel Parameters (passing board info data):
         *   r3: ptr to board info data
         *   r4: initrd_start or 0 if no initrd
         *   r5: initrd_end - unused if r4 is 0
         *   r6: Start of command line string
         *   r7: End   of command line string
         */
        debug ("   Booting using board info.../n");
        (*kernel) (kbd, initrd_start, initrd_end, cmd_start, cmd_end);
        /* does not return */
    }

如果未定义CONFIG_OF_LIBFDT或者当前bootm命令没有FDT参数时则采用传统的方式启动内核。

启动命令格式如下:
Bootm kernel_addr  ramdisk_addr/-  fdt_addr
当不采用ramdisk时,第二个参数为“-”

5    内核如何解析设备树
1)首先将从u-boot 传递过来的映像基地址和dtb 文件映像基地址保存通用寄存器r30,r31;
2)通过调用machine_init()、early_init_devtree()函数来获取内核前期初始化所需的bootargs,cmd_line等系统引导参数;

3)调用start_kernel()、setup_arch()、unflatten_device_tree()函数来解析dtb 文件,构建一个由device_node 结构连接而成的单项链表,并使用全局变量allnodes 指针来保存这个链表的头指针;
4)内核调用OF 提供的API 函数获取allnodes链表信息来初始化内核其他子系统、设备等。
 

Head_32.S
/*
 * This is where the main kernel code starts.
 */
start_here:
。。。。
/*
 * Do early platform-specific initialization,
 * and set up the MMU.
 */
    mr    r3,r31
    mr    r4,r30
    bl    machine_init

/*
 * Find out what kind of machine we're on and save any data we need
 * from the early boot process (devtree is copied on pmac by prom_init()).
 * This is called very early on the boot process, after a minimal
 * MMU environment has been set up but before MMU_init is called.
 */
void __init machine_init(unsigned long dt_ptr, unsigned long phys)
{
    /* If btext is enabled, we might have a BAT setup for early display,
     * thus we do enable some very basic udbg output
     */
#ifdef CONFIG_BOOTX_TEXT
    udbg_putc = btext_drawchar;
#endif

    /* Do some early initialization based on the flat device tree */
    early_init_devtree(__va(dt_ptr));
}

/* Warning, IO base is not yet inited */
void __init setup_arch(char **cmdline_p)
{
    *cmdline_p = cmd_line;

    /* so udelay does something sensible, assume <= 1000 bogomips */
    loops_per_jiffy = 500000000 / HZ;

    unflatten_device_tree();
…..
}

/**
 * unflattens the device-tree passed by the firmware, creating the
 * tree of struct device_node. It also fills the "name" and "type"
 * pointers of the nodes so the normal device-tree walking functions
 * can be used (this used to be done by finish_device_tree)
 */
void __init unflatten_device_tree(void)
{
    unsigned long start, mem, size;
    struct device_node **allnextp = &allnodes;

    DBG(" -> unflatten_device_tree()/n");

    /* First pass, scan for size */
    start = ((unsigned long)initial_boot_params) +
        initial_boot_params->off_dt_struct;
    size = unflatten_dt_node(0, &start, NULL, NULL, 0);
    size = (size | 3) + 1;

    DBG("  size is %lx, allocating.../n", size);

    /* Allocate memory for the expanded device tree */
    mem = lmb_alloc(size + 4, __alignof__(struct device_node));
    mem = (unsigned long) __va(mem);

    ((u32 *)mem)[size / 4] = 0xdeadbeef;

    DBG("  unflattening %lx.../n", mem);

    /* Second pass, do actual unflattening */
    start = ((unsigned long)initial_boot_params) +
        initial_boot_params->off_dt_struct;
    unflatten_dt_node(mem, &start, NULL, &allnextp, 0);
    if (*((u32 *)start) != OF_DT_END)
        printk(KERN_WARNING "Weird tag at end of tree: %08x/n", *((u32 *)start));
    if (((u32 *)mem)[size / 4] != 0xdeadbeef)
        printk(KERN_WARNING "End of tree marker overwritten: %08x/n",
               ((u32 *)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");

    DBG(" <- unflatten_device_tree()/n");
}

6    设备树对驱动设计产生的影响
TBD

阅读更多
换一批

没有更多推荐了,返回首页