在早期的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。
在System Type->SAMSUNG S3C24XX SoCs SupportSamsuug选择Samsung S3C2416 machine using devicetree。
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的支持。
g) &pinctrl_0、&rtc、&sdhci_0、&uart_0等结点中,pinctrl_0、rtc、sdhci_0、uart_0为节点标号,通过”&”对相应的节点进行引用,如&rtc引用的是在s3c2416.dtsi文件中的rtc:rtc@57000000 {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";
};
};
interrupt-controller@4a000000{
compatible= "samsung,s3c2416-irq";
};
clocks:clock-controller@0x4c000000 {
compatible= "samsung,s3c2416-clock";
reg =<0x4c000000 0x40>;
#clock-cells= <1>;
};
pinctrl@56000000{
compatible= "samsung,s3c2416-pinctrl";
};
timer@51000000{
clocks= <&clocks PCLK_PWM>;
clock-names= "timers";
};
uart_0: serial@50000000{
compatible= "samsung,s3c2440-uart";
clock-names= "uart", "clk_uart_baud2",
"clk_uart_baud3";
clocks= <&clocks PCLK_UART0>, <&clocks PCLK_UART0>,
<&clocksSCLK_UART>;
};
uart_1:serial@50004000 {
compatible= "samsung,s3c2440-uart";
clock-names= "uart", "clk_uart_baud2",
"clk_uart_baud3";
clocks= <&clocks PCLK_UART1>, <&clocks PCLK_UART1>,
<&clocksSCLK_UART>;
};
uart_2:serial@50008000 {
compatible= "samsung,s3c2440-uart";
clock-names= "uart", "clk_uart_baud2",
"clk_uart_baud3";
clocks= <&clocks PCLK_UART2>, <&clocks PCLK_UART2>,
<&clocksSCLK_UART>;
};
uart_3:serial@5000C000 {
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:sdhci@4AC00000 {
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:sdhci@4A800000 {
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:watchdog@53000000 {
interrupts= <1 9 27 3>;
clocks= <&clocks PCLK_WDT>;
clock-names= "watchdog";
};
rtc:rtc@57000000 {
compatible= "samsung,s3c2416-rtc";
clocks= <&clocks PCLK_RTC>;
clock-names= "rtc";
};
i2c@54000000{
compatible ="samsung,s3c2440-i2c";
clocks= <&clocks PCLK_I2C0>;
clock-names= "i2c";
};
};
a) aliases结点定义了一些别名,可以省写了引用结点时的完整路径。 b) cpus结点,其子结点描述了在系统中的每个cpu属性。 c) interrupt-controller@4a000000、clock-controller@0x4c000000、pinctrl@56000000等等结点中,”@”前面的为结点名,后面的为单元地址,一般对应结点的reg属性地址。 d) timer@51000000、uart_0: serial@50000000、watchdog: watchdog@53000000等等结点中,用clocks属性描述对应设备使用的clock source,clock provider为clocks:clock-controller@0x4c000000结点。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:interrupt-controller@4a000000 {
compatible = "samsung,s3c2410-irq";
reg = <0x4a000000 0x100>;
interrupt-controller;
#interrupt-cells = <4>;
};
pinctrl_0: pinctrl@56000000 {
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>;
};
};
timer@51000000 {
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: serial@50000000 {
compatible = "samsung,s3c2410-uart";
reg = <0x50000000 0x4000>;
interrupts = <1 28 0 4>, <1 28 1 4>;
status = "disabled";
};
uart1: serial@50004000 {
compatible = "samsung,s3c2410-uart";
reg = <0x50004000 0x4000>;
interrupts = <1 23 3 4>, <1 23 4 4>;
status = "disabled";
};
uart2: serial@50008000 {
compatible = "samsung,s3c2410-uart";
reg = <0x50008000 0x4000>;
interrupts = <1 15 6 4>, <1 15 7 4>;
status = "disabled";
};
watchdog@53000000 {
compatible = "samsung,s3c2410-wdt";
reg = <0x53000000 0x100>;
interrupts = <0 0 9 3>;
status = "disabled";
};
rtc@57000000 {
compatible = "samsung,s3c2410-rtc";
reg = <0x57000000 0x100>;
interrupts = <0 0 30 3>, <0 0 8 3>;
status = "disabled";
};
i2c@54000000 {
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:interrupt-controller@4a000000结点中,用boolean值interrupt-controller属性表明当前结点是一个中断控制器。#interrupt-cell用来标识这个中断控制器需要多少个cell的中断描述符。 c) i2c@54000000等结点中,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
在控制台对brightness文件写入0或1控制led灯的亮灭。
4. 附录
基于s3c2416 mdk工程实现,用于从sd卡加载内核、dtb文件到ram,以device tree方式启动linux内核。