linux设备树启动

时间:2024-05-20 22:15:37

linux设备树启动

象棋小子   1048272975

在早期的arm linux内核中,板级的细节信息使用代码的形式,存放在arch/arm目录中,这些板级代码只对相应的开发板有用,却被硬编码进linux内核,显然这不是一种好的方法。Device Tree是一种描述硬件的数据结构,它包含了板级硬件细节信息,通过Device Tree,可以把硬件信息传递给内核,而不需要再硬编码了。

1. s3c2416设备树

Linux内核已经完整支持s3c2416的设备树,基于s3c2416平台的目标板,可以不用更改内核代码,不同设备,不同的板级配置在Device Tree中实现。

1.1. 修改主目录Makefile

Makefile默认获取得是主机的架构以及编译环境,我们的内核是要运行在arm平台上,所以修改ARCH=arm,CROSS_COMPILE=arm-linux-。

1.2. mach-s3c2416-dt.c

在mach-s3c24xx目录下已实现了s3c24xx系列的平台代码,其中mach-s3c2416-dt.c实现了基于s3c2416设备树的机器描述符定义。

Linux内核通过machine_desc结构体来控制系统体系架构相关部分的初始化,其通过DT_MACHINE_START/MACHINE_END宏定义进行描述。

DT_MACHINE_START(S3C2416_DT,"Samsung S3C2416 (Flattened Device Tree)")

     .dt_compat =s3c2416_dt_compat,

     .map_io     =s3c2416_dt_map_io,

     .init_irq      =irqchip_init,

     .init_machine   = s3c2416_dt_machine_init,

MACHINE_END

Linux内核在启动时会配对bootloader传递的dtb(device tree binary),当配对到s3c2416_dt_compat,则获取s3c2416的机器描述符进行片上系统的初始化。从start_kernel()开始启动内核,按顺序调用machine_desc中的map_io()、init_irq()、init_machine()。

start_kernel()->setup_arch()->paging_init()->devicemaps_init()->map_io()实现调用s3c2416_dt_map_io (),默认外设IO资源地址不在Linux内核空间(3G~4G),需访问一些外设IO资源,可以在初始化页表时,静态映射IO资源空间到内核地址空间。

start_kernel()->init_IRQ()->init_irq()实现调用irqchip_init (),该函数将查找__irqchip_of_table,匹配到Device Tree中的中断控制器,调用相应的初始化函数,把特定CPU的中断信息注册进内核中断子系统中,使内核能响应并处理相应的中断。对于s3c2416,在drivers\irqchip\irq-s3c24xx.c中声明了相应的中断控制器。

IRQCHIP_DECLARE(s3c2416_irq,"samsung,s3c2416-irq", s3c2416_init_intc_of);

kernel_init()->kernel_init_freeable()->do_basic_setup()->do_initcalls()实现调用s3c2416_dt_machine_init (),Linux在启动的最后会创建kernel_init进程,该进程中会初始化调用所有在”initcall”段的函数,arch_initcall(customize_machine)实现从customize_machine()中调用s3c2416_dt_machine_init(),该函数调用of_platform_populate(),把Device Tree中需要加载为device的device node转为platform device,用于匹配对应的设备驱动。

1.3. 内核配置

在Device Tree中实现板级的支持,在arch\arm\boot\dts目录,拷贝s3c2416-smdk2416.dts并命名成s3c2416-home2416.dts,作为HOME2416的板级配置,并在该目录Makefile文件中加入s3c2416-home2416.dts的设备树编译支持。

dtb-$(CONFIG_ARCH_S3C24XX)+= \

     s3c2416-smdk2416.dtb \

     s3c2416-home2416.dts

我们基于s3c2410的配置文件进行配置,把arch\arm\configs文件夹中的s3c2410_defconfig拷贝到主目录,并命名成.config。

执行make menuconfig,进行Linux内核的配置。在Boot option下选择FlattenedDevice Tree support。

linux设备树启动

在System Type->SAMSUNG S3C24XX SoCs SupportSamsuug选择Samsung S3C2416 machine using devicetree。

linux设备树启动

1.4. 编译

执行make,进行编译,成功后会在arch\arm\boot目录下生成Image非压缩内核以及zImage压缩内核,在arch\arm\boot\dts目录中生成s3c2416-home2416.dtb的二进制设备树。

2. dts

dts即Device Tree Source设备树源码,它是一种ascii文本格式,可以用来描述硬件配置和系统运行参数。通过dtc(Device Tree Compiler),可以将Device Tree源码编译成适合机器处理的dtb文件,即Device Tree binary。在系统启动的时候,bootloader可以把储存在外部设备的dtb加载到内存中,并在跳转执行内核的同时,把dtb的起始位置传递给内核,由内核对dtb进行处理。

Device Tree由一系列被命名的结点(node)和属性(property)组成,而结点本身可包含子结点,所谓属性,就是成对出现的name和value。linux内核设备驱动将会引用这些结点和属性。

此处以内核源码中s3c2416-smdk2416.dts为基础,加入了leds结点用于支持leds驱动,s3c2416-home2416.dts简单示例如下。

2.1. s3c2416-home2416.dts

.dts文件用于板级定义。

/dts-v1/;

#include<dt-bindings/gpio/gpio.h>

#include"s3c2416.dtsi"

 

/ {

       model = "HOME2416";

       compatible = "samsung,s3c2416";

 

       memory {

              reg =  <0x30000000 0x4000000>;

       };

 

       chosen {

bootargs = "noinitrd root=/dev/mtdblock3 rootfstype=yaffs2 init=/init console=ttySAC0";     

       };

 

       clocks {

              compatible ="simple-bus";

              #address-cells = <1>;

              #size-cells = <1>;

 

              xti: xti {

                     compatible = "fixed-clock";

                     clock-frequency =<12000000>;

                     clock-output-names ="xti";

                     #clock-cells = <0>;

              };

       };

 

       leds {

              compatible ="gpio-leds";

              pinctrl-names ="default";

              pinctrl-0 =<&gpio_leds>;

 

              led-4 {

                     label = "LED4";

                     gpios = <&gpe 13 GPIO_ACTIVE_HIGH>;

                     linux,default-trigger ="heartbeat";

              };

              led-7 {

                     label = "LED7";

                     gpios = <&gpe 12GPIO_ACTIVE_HIGH>;

                     linux,default-trigger ="timer";

              };

       };

 

};

 

&pinctrl_0 {

       gpio_leds: gpio-leds {

              samsung,pins = "gpe-12","gpe-13";

              samsung,pin-pud =<EXYNOS_PIN_PULL_NONE>;

       };

};

 

&rtc {

       status = "okay";

};

 

&sdhci_0 {

       pinctrl-names = "default";

       pinctrl-0 = <&sd1_clk>,<&sd1_cmd>,

                     <&sd1_bus1>,<&sd1_bus4>;

       bus-width = <4>;

       cd-gpios = <&gpf 3 0>;

       cd-inverted;

       status = "okay";

};

 

&sdhci_1 {

       pinctrl-names = "default";

       pinctrl-0 = <&sd0_clk>,<&sd0_cmd>,

                     <&sd0_bus1>,<&sd0_bus4>;

       bus-width = <4>;

       broken-cd;

       status = "okay";

};

 

&uart_0 {

       status = "okay";

       pinctrl-names = "default";

       pinctrl-0 = <&uart0_data>,<&uart0_fctl>;

};

 

&uart_1 {

       status = "okay";

       pinctrl-names = "default";

       pinctrl-0 = <&uart1_data>,<&uart1_fctl>;

};

 

&uart_2 {

       status = "okay";

       pinctrl-names = "default";

       pinctrl-0 = <&uart2_data>;

};

 

&uart_3 {

       status = "okay";

       pinctrl-names = "default";

       pinctrl-0 = <&uart3_data>;

};

 

&watchdog {

       status = "okay";

};

a)    dts可以分成多个文件,通过include进行包含。.dts包含板级的描述信息,.dtsi为头文件,描述一些片上系统级之类的通用信息。dts支持c预处理器,也可以包含c头文件。

b)   “/”为根结点,model属性为板级名称,compatible属性用于匹配相应的平台,用于平台的初始化,对应machine_desc结构体中的.dt_compat。

c)    memory结点定义了内存的地址以及大小。

d)   chosen结点用来给内核传递参数,可以用bootargs定义linux内核启动时的命令行。

e)    clocks结点compatible ="simple-bus"表示无需驱动的简单内存映射总线,它的子结点会被注册为platform device。

f)     leds结点为针对HOME2416添加的leds驱动设备树描述。子结点用compatible属性来匹配相应的设备驱动,compatible = "gpio-leds",即结点用来匹配"gpio-leds"驱动,该驱动文件为drivers\leds\Leds-gpio.c。可以在Documentation/devicetree/bindings目录查看结点和属性应该如何来描述设备的硬件细节的,例如”gpio-leds”对应的帮助文件为Documentation\devicetree\bindings\leds\leds-gpio.txt,可以从该文件了解leds结点应该如何描述。

当linux内核从Device Tree中注册了leds平台设备,需要匹配到对应的设备驱动,因此linux内核需要支持"gpio-leds"驱动,Device Drivers> LED Support > LED Trigger support选中LED Timer Trigger以及LED HeartbeatTrigger的支持。

linux设备树启动

g)   &pinctrl_0、&rtc、&sdhci_0、&uart_0等结点中,pinctrl_0、rtc、sdhci_0、uart_0为节点标号,通过”&”对相应的节点进行引用,如&rtc引用的是在s3c2416.dtsi文件中的rtc:[email protected] {xxx}结点,实现后面定义的结点属性覆盖、添加进前面定义的结点属性中。

h)   leds 、&sdhci_0pinctrl、&uart_0等结点中的pinctrl-0为对应设备的引脚复用配置,pinctrl子系统用于管理引脚的复用,pinctrl-names属性为引脚配置状态的名称,pinctrl-0属性为引脚配置列表。在设备probed时,”default”名称的pinctrl状态会自动应用。

i)     &rtc、&sdhci_0、&watchdog等结点具有” status”属性,status = "okay"表示设备被使能,相应的device node才会转为platformdevice,并匹配对应的驱动。

2.2. s3c2416.dtsi

.dtsi为头文件,描述一些片上系统级之类的通用信息。

#include <dt-bindings/clock/s3c2443.h>

#include "s3c24xx.dtsi"

#include "s3c2416-pinctrl.dtsi"

 

/ {

       model ="Samsung S3C2416 SoC";

       compatible ="samsung,s3c2416";

 

       aliases {

              serial3= &uart_3;

       };

 

       cpus {

              #address-cells= <1>;

              #size-cells= <0>;

 

              cpu {

                     compatible= "arm,arm926ej-s";

              };

       };

 

       [email protected]{

              compatible= "samsung,s3c2416-irq";

       };

 

       clocks:[email protected] {

              compatible= "samsung,s3c2416-clock";

              reg =<0x4c000000 0x40>;

              #clock-cells= <1>;

       };

 

       [email protected]{

              compatible= "samsung,s3c2416-pinctrl";

       };

 

       [email protected]{

              clocks= <&clocks PCLK_PWM>;

              clock-names= "timers";

       };

 

       uart_0: [email protected]{

              compatible= "samsung,s3c2440-uart";

              clock-names= "uart", "clk_uart_baud2",

                            "clk_uart_baud3";

              clocks= <&clocks PCLK_UART0>, <&clocks PCLK_UART0>,

                            <&clocksSCLK_UART>;

       };

 

       uart_1:[email protected] {

              compatible= "samsung,s3c2440-uart";

              clock-names= "uart", "clk_uart_baud2",

                            "clk_uart_baud3";

              clocks= <&clocks PCLK_UART1>, <&clocks PCLK_UART1>,

                            <&clocksSCLK_UART>;

       };

 

       uart_2:[email protected] {

              compatible= "samsung,s3c2440-uart";

              clock-names= "uart", "clk_uart_baud2",

                            "clk_uart_baud3";

              clocks= <&clocks PCLK_UART2>, <&clocks PCLK_UART2>,

                            <&clocksSCLK_UART>;

       };

 

       uart_3:[email protected] {

              compatible= "samsung,s3c2440-uart";

              reg =<0x5000C000 0x4000>;

              interrupts= <1 18 24 4>, <1 18 25 4>;

              clock-names= "uart", "clk_uart_baud2",

                            "clk_uart_baud3";

              clocks= <&clocks PCLK_UART3>, <&clocks PCLK_UART3>,

                            <&clocksSCLK_UART>;

              status= "disabled";

       };

 

       sdhci_1:[email protected] {

              compatible= "samsung,s3c6410-sdhci";

              reg =<0x4AC00000 0x100>;

              interrupts= <0 0 21 3>;

              clock-names= "hsmmc", "mmc_busclk.0",

                            "mmc_busclk.2";

              clocks= <&clocks HCLK_HSMMC0>, <&clocks HCLK_HSMMC0>,

                            <&clocksMUX_HSMMC0>;

              status= "disabled";

       };

 

       sdhci_0:[email protected] {

              compatible= "samsung,s3c6410-sdhci";

              reg =<0x4A800000 0x100>;

              interrupts= <0 0 20 3>;

              clock-names= "hsmmc", "mmc_busclk.0",

                            "mmc_busclk.2";

              clocks= <&clocks HCLK_HSMMC1>, <&clocks HCLK_HSMMC1>,

                            <&clocksMUX_HSMMC1>;

              status= "disabled";

       };

 

       watchdog:[email protected] {

              interrupts= <1 9 27 3>;

              clocks= <&clocks PCLK_WDT>;

              clock-names= "watchdog";

       };

 

       rtc:[email protected] {

              compatible= "samsung,s3c2416-rtc";

              clocks= <&clocks PCLK_RTC>;

              clock-names= "rtc";

       };

 

       [email protected]{

              compatible ="samsung,s3c2440-i2c";

              clocks= <&clocks PCLK_I2C0>;

              clock-names= "i2c";

       };

};

a)   aliases结点定义了一些别名,可以省写了引用结点时的完整路径。

b)  cpus结点,其子结点描述了在系统中的每个cpu属性。

c)   [email protected][email protected][email protected]等等结点中,”@”前面的为结点名,后面的为单元地址,一般对应结点的reg属性地址。

d)  [email protected]、uart_0: [email protected]、watchdog: [email protected]等等结点中,用clocks属性描述对应设备使用的clock source,clock provider为clocks:[email protected]结点。clock-names属性用来描述该设备时钟源名称。clocks属性由内核clk_get函数所使用。

2.3. s3c24xx.dtsi

#include "skeleton.dtsi"

 

/ {

       compatible = "samsung,s3c24xx";

       interrupt-parent = <&intc>;

 

       aliases {

              pinctrl0 = &pinctrl_0;

              serial0 = &uart0;

              serial1 = &uart1;

              serial2 = &uart2;

       };

 

       intc:[email protected] {

              compatible = "samsung,s3c2410-irq";

              reg = <0x4a000000 0x100>;

              interrupt-controller;

              #interrupt-cells = <4>;

       };

 

       pinctrl_0: [email protected] {

              reg = <0x56000000 0x1000>;

 

              wakeup-interrupt-controller {

                     compatible ="samsung,s3c2410-wakeup-eint";

                     interrupts = <0 0 0 3>,

                                 <0 0 1 3>,

                                <0 0 2 3>,

                                <0 0 3 3>,

                                <0 0 4 4>,

                                <0 0 5 4>;

              };

       };

 

       [email protected] {

              compatible = "samsung,s3c2410-pwm";

              reg = <0x51000000 0x1000>;

              interrupts = <0 0 10 3>, <0 0 11 3>, <00 12 3>, <0 0 13 3>, <0 0 14 3>;

              #pwm-cells = <4>;

       };

 

       uart0: [email protected] {

              compatible = "samsung,s3c2410-uart";

              reg = <0x50000000 0x4000>;

              interrupts = <1 28 0 4>, <1 28 1 4>;

              status = "disabled";

       };

 

       uart1: [email protected] {

              compatible = "samsung,s3c2410-uart";

              reg = <0x50004000 0x4000>;

              interrupts = <1 23 3 4>, <1 23 4 4>;

              status = "disabled";

       };

 

       uart2: [email protected] {

              compatible = "samsung,s3c2410-uart";

              reg = <0x50008000 0x4000>;

              interrupts = <1 15 6 4>, <1 15 7 4>;

              status = "disabled";

       };

 

       [email protected] {

              compatible = "samsung,s3c2410-wdt";

              reg = <0x53000000 0x100>;

              interrupts = <0 0 9 3>;

              status = "disabled";

       };

 

       [email protected] {

              compatible = "samsung,s3c2410-rtc";

              reg = <0x57000000 0x100>;

              interrupts = <0 0 30 3>, <0 0 8 3>;

              status = "disabled";

       };

 

       [email protected] {

              compatible = "samsung,s3c2410-i2c";

              reg = <0x54000000 0x100>;

              interrupts = <0 0 27 3>;

              #address-cells = <1>;

              #size-cells = <0>;

              status = "disabled";

       };

};

a)   interrupt-parent属性表明当前结点所使用的中断控制器。

b)  intc:[email protected]结点中,用boolean值interrupt-controller属性表明当前结点是一个中断控制器。#interrupt-cell用来标识这个中断控制器需要多少个cell的中断描述符。

c)   [email protected]等结点中,reg属性表示设备使用的地址信息,#address-cells标识在reg属性中基地址需要多少个cell(32位),#size-cells标识在reg属性中地址范围大小需要多少个cell,由内核platform_get_resource函数获取对应所需的IO资源。interrupts属性表示一个中断标识符列表,相应的每一个中断输出信号,由内核platform_get_irq函数获取对应所需的中断资源。

2.4. s3c2416-pinctrl.dtsi

s3c2416-pinctrl.dtsi描述了s3c2416相应外设的引脚配置。

&pinctrl_0 {

       /*

        * Pin banks

        */

 

       gpa: gpa {

              gpio-controller;

              #gpio-cells = <2>;

       };

 

       gpb: gpb {

              gpio-controller;

              #gpio-cells = <2>;

       };

 

       gpc: gpc {

              gpio-controller;

              #gpio-cells = <2>;

       };

 

       gpd: gpd {

              gpio-controller;

              #gpio-cells = <2>;

       };

 

       gpe: gpe {

              gpio-controller;

              #gpio-cells = <2>;

       };

 

       gpf: gpf {

              gpio-controller;

              #gpio-cells = <2>;

              interrupt-controller;

              #interrupt-cells = <2>;

       };

 

       gpg: gpg {

              gpio-controller;

              #gpio-cells = <2>;

              interrupt-controller;

              #interrupt-cells = <2>;

       };

 

       gph: gph {

              gpio-controller;

              #gpio-cells = <2>;

       };

 

       gpj: gpj {

              gpio-controller;

              #gpio-cells = <2>;

       };

 

       gpk: gpk {

              gpio-controller;

              #gpio-cells = <2>;

       };

 

       gpl: gpl {

              gpio-controller;

              #gpio-cells = <2>;

       };

 

       gpm: gpm {

              gpio-controller;

              #gpio-cells = <2>;

       };

 

       /*

        * Pin groups

        */

 

       uart0_data: uart0-data {

              samsung,pins = "gph-0", "gph-1";

              samsung,pin-function = <2>;

       };

 

       uart0_fctl: uart0-fctl {

              samsung,pins = "gph-8", "gph-9";

              samsung,pin-function = <2>;

       };

 

       uart1_data: uart1-data {

              samsung,pins = "gph-2", "gph-3";

              samsung,pin-function = <2>;

       };

 

       uart1_fctl: uart1-fctl {

              samsung,pins = "gph-10", "gph-11";

              samsung,pin-function = <2>;

       };

 

       uart2_data: uart2-data {

              samsung,pins = "gph-4", "gph-5";

              samsung,pin-function = <2>;

       };

 

       uart2_fctl: uart2-fctl {

              samsung,pins = "gph-6", "gph-7";

              samsung,pin-function = <2>;

       };

 

       uart3_data: uart3-data {

              samsung,pins = "gph-6", "gph-7";

              samsung,pin-function = <2>;

       };

 

       extuart_clk: extuart-clk {

              samsung,pins = "gph-12";

              samsung,pin-function = <2>;

       };

 

       i2c0_bus: i2c0-bus {

              samsung,pins = "gpe-14", "gpe-15";

              samsung,pin-function = <2>;

       };

 

       spi0_bus: spi0-bus {

              samsung,pins = "gpe-11", "gpe-12","gpe-13";

              samsung,pin-function = <2>;

       };

 

       sd0_clk: sd0-clk {

              samsung,pins = "gpe-5";

              samsung,pin-function = <2>;

       };

 

       sd0_cmd: sd0-cmd {

              samsung,pins = "gpe-6";

              samsung,pin-function = <2>;

       };

 

       sd0_bus1: sd0-bus1 {

              samsung,pins = "gpe-7";

              samsung,pin-function = <2>;

       };

 

       sd0_bus4: sd0-bus4 {

              samsung,pins = "gpe-8", "gpe-9","gpe-10";

              samsung,pin-function = <2>;

       };

 

       sd1_cmd: sd1-cmd {

              samsung,pins = "gpl-8";

              samsung,pin-function = <2>;

       };

 

       sd1_clk: sd1-clk {

              samsung,pins = "gpl-9";

              samsung,pin-function = <2>;

       };

 

       sd1_bus1: sd1-bus1 {

              samsung,pins = "gpl-0";

              samsung,pin-function = <2>;

       };

 

       sd1_bus4: sd1-bus4 {

              samsung,pins = "gpl-1", "gpl-2","gpl-3";

              samsung,pin-function = <2>;

       };

};

a)   gpa: gpa、gpb: gpb、gpc: gpc等结点中,用boolean值gpio-controller属性表明当前结点是一个gpio控制器。#gpio-cells用来标识这个gpio控制器需要多少个cell的描述符。

2.5. skeleton.dtsi

skeleton.dtsi为各arm架构芯片共用的一些硬件定义信息。

/ {

       #address-cells = <1>;

       #size-cells = <1>;

       chosen { };

       aliases { };

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

};

3. 内核运行

内核编译后,在arch\arm\boot目录下生成Image非压缩内核以及zImage压缩内核,在arch\arm\boot\dts目录中生成s3c2416-home2416.dtb的二进制设备树。此处内核采用initramfs,基于busybox的rootfs编译进内核,bootloader在启动内核时需要把内核加载进ram,同时需要把s3c2416-home2416.dtb也加载进内核,跳转内核时指定r0为0,r1不重要,r2为dtb存放的ram地址。基于mdk的s3c2416 bootloader如下,实现从sd卡加载内核、dtb到ram后,跳转启动内核。

       KernelBase = DRAM_BASE+LINUX_KERNEL_OFFSET;

       DtbBase = DRAM_BASE+LINUX_DTB_OFFSET;

      

       FileSize =Sd_ReadFileToRam("/image/s3c2416-home2416.dtb", (uint8_t *)DtbBase);

       if (FileSize < 0) {

              return -1;

       }     

       FileSize =Sd_ReadFileToRam("/image/kernel.bin", (uint8_t *)KernelBase);

       if (FileSize < 0) {

              return -1;

       }

       Kernel = (void (*)(uint32_t, uint32_t,uint32_t))KernelBase;

       Kernel(0, 0, DtbBase); // never return

linux设备树启动

在控制台对brightness文件写入0或1控制led灯的亮灭。

linux设备树启动

4. 附录

基于s3c2416 mdk工程实现,用于从sd卡加载内核、dtb文件到ram,以device tree方式启动linux内核。

https://pan.baidu.com/s/1kUNjQUr