Device Tree 詳解

1、DTS語法

對於DeviceTree的來歷和用處大部分人都已經非常瞭解了,DeviceTree發源於PowerPC架構,爲了消除代碼中冗餘的各種device註冊代碼而產生的,現在已經成爲了linux的通用機制。

DeviceTree的結構非常簡單,由兩種元素組成:Node(節點)、Property(屬性)。下圖是一個真實的簡單的DeviceTree樹形結構圖。

這裏寫圖片描述

  • Node節點。在DTS中使用一對花括號”node-name{}”來定義;
  • Property屬性。在Node中使用”property-name=value”字符串來定義;
/ {
    model = "mt6799";
    compatible = "mediatek,mt6799";
    interrupt-parent = <&gic>;
    #address-cells = <2>;
    #size-cells = <2>;

    /* chosen */
    chosen {
        bootargs = "console=tty0 console=ttyMT0,921600n1 root=/dev/ram";
    };
}

上述例子中定義了一個根節點”/”和一個子節點“chosen”,其他的字符串“model = “mt6799”;”、“compatible = “mediatek,mt6799”;”都是property。

Node、Property的名字和值都是可以自定義的,沒有太大限制。但是DeviceTree的標準還是預定義了一些標準的Node和Property,在標準Node和Property之間還定義了一些約束規則。關於這些描述在 The DeviceTree Specification官方spec中有詳細描述。這裏爲了方便大家,還是重複做一些搬運。

1.1、標準Property

Property的格式爲”property-name=value”,其中value的取值類型如下:

Property values:
Value Description
<empty> Value is empty. Used for conveying true-false information, when the presence of absence of the property itself is sufficiently descriptive.

Property值爲空,用property本身出現或者不出現來表示一個treue/false值。
<u32> A 32-bit integer in big-endian format. Example: the 32-bit value 0x11223344 would be represented in memory as:
address 11
address+1 22
address+2 33
address+3 44

32bit的值,用大端格式存儲。
<u64> Represents a 64-bit integer in big-endian format. Consists of two <u32> values where the first value contains the most significant bits of the integer and the second value contains the least significant bits.
Example: the 64-bit value 0x1122334455667788 would be represented as two cells as: .
The value would be represented in memory as:
address 11
address+1 22
address+2 33
address+3 44
address+4 55
address+5 66
address+6 77
address+7 88

64bit的值,用大端格式存儲。
<string> Strings are printable and null-terminated. Example: the string “hello” would be represented in memory as:
address 68 ‘h’
address+1 65 ‘e’
address+2 6C ‘l’
address+3 6C ‘l’
address+4 6F ‘o’
address+5 00 ‘\0’

字符串。
<prop-encoded-array> Format is specific to the property. See the property definition.

混合編碼,自定義property的值。
<phandle> A <u32> value. A phandle value is a way to reference another node in the devicetree.
Any node that can be referenced defines a phandle property with a unique <u32> value. That number is used for the value of properties with a phandle value type.

作爲一個句柄指向一個Node,用來引用Node。
<stringlist> A list of <string> values concatenated together.
Example: The string list “hello”,”world” would be represented in memory as:
address 68 ‘h’
address+1 65 ‘e’
address+2 6C ‘l’
address+3 6C ‘l’
address+4 6F ‘o’
address+5 00 ‘\0’
address+6 77 ‘w’
address+7 6f ‘o’
address+8 72 ‘r’
address+9 6C ‘l’
address+10 64 ‘d’
address+11 00 ‘\0’

字符串數組。

1.1.1、compatible

  • “compatible”屬性通常用來device和driver的適配,推薦的格式爲”manufacturer,model”。
Property name: compatible
Value type: <stringlist>
Description: The compatible property value consists of one or more strings that define the specific programming model for the device. This list of strings should be used by a client program for device driver selection. The property value consists of a concatenated list of null terminated strings, from most specific to most general. They allow a device to express its compatibility with a family of similar devices, potentially allowing a single device driver to match against several devices.
The recommended format is “manufacturer,model”, where manufacturer is a string describing the name of the manufacturer (such as a stock ticker symbol), and model specifies the model number.

Example: compatible = “fsl,mpc8641”, “ns16550”;

In this example, an operating system would first try to locate a device driver that supported fsl,mpc8641. If a driver was not found, it would then try to locate a driver that supported the more general ns16550 device type.

在這個例子中,device首先嚐試去適配”fsl,mpc8641”driver,如果失敗再去嘗試適配”ns16550”driver。

1.1.2、model

  • “model”屬性只是簡單的表示型號,root節點用其來傳遞值給machine_desc_str。
Property name: model
Value type: <stringlist>
Description: The model property value is a <string> that specifies the manufacturer’s model number of the device.
The recommended format is: “manufacturer,model”, where manufacturer is a string describing the name of the manufacturer (such as a stock ticker symbol), and model specifies the model number.
Example: model = “fsl,MPC8349EMITX”;

1.1.3、phandle

  • “phandle”屬性通用一個唯一的id來標識一個Node,在property可以使用這個id來引用Node。
Property name: phandle
Value type: <u32>
Description: The phandle property specifies a numerical identifier for a node that is unique within the devicetree. The phandle property value is used by other nodes that need to refer to the node associated with the property.
Example: See the following devicetree excerpt:
pic@10000000 {
phandle = ;
interrupt-controller;
};

A phandle value of 1 is defined. Another device node could reference the pic node with a phandle value of 1:
another-device-node {
interrupt-parent = ;
};

Node“pic@10000000”定義了一個phandle屬性,這個phandle有唯一id = ,在property“interrupt-parent”通過對Node“pic@10000000”進行引用。

在DeviceTree中通過另一種方式進行phandle的定義和引用更加常見:

  • 定義一個“label:”來引用Node,在編譯是系統會自動爲node生成一個phandle屬性。”cpu0”是一個label,用來引用node”cpu@0”:
        cpu0: cpu@0 {
            device_type = "cpu";
            compatible = "arm,cortex-a35";
            reg = <0x000>;
            enable-method = "psci";
            cpu-idle-states = <&LEGACY_MCDI &LEGACY_SODI &LEGACY_SODI3 &LEGACY_DPIDLE>,
                      <&LEGACY_SUSPEND &MCDI &SODI &SODI3 &DPIDLE &SUSPEND>;
            cpu-release-addr = <0x0 0x40000200>;
            clock-frequency = <1248000000>;
        };
  • 使用”&”來引用“label”,即是引用phandle。property”cpu”通過”&cpu0”來對node”cpu@0”:
        cpu-map {
            cluster0 {
                core0 {
                    cpu = <&cpu0>;
                };


                core1 {
                    cpu = <&cpu1>;
                };

                core2 {
                    cpu = <&cpu2>;
                };

                core3 {
                    cpu = <&cpu3>;
                };

            };

1.1.4、#address-cells 、 #size-cells

  • “#address-cells, #size-cells”屬性用來定義當前node的子node中”reg”屬性的解析格式。
Property name: #address-cells, #size-cells
Value type: <u32>
Description: The #address-cells and #size-cells properties may be used in any device node that has children in the devicetree hierarchy and describes how child device nodes should be addressed. The #address-cells property defines the number of <u32> cells used to encode the address field in a child node’s reg property. The #size-cells property defines the number of <u32> cells used to encode the size field in a child node’s reg property.
The #address-cells and #size-cells properties are not inherited from ancestors in the devicetree. They shall be explicitly defined.
A DTSpec-compliant boot program shall supply #address-cells and #size-cells on all nodes that have children.
If missing, a client program should assume a default value of 2 for #address-cells, and a value of 1 for #size-cells.
Example: See the following devicetree excerpt:
soc {
#address-cells = <1>;
#size-cells = <1>;
    serial {
    compatible = “ns16550”;
    reg = <0x4600 0x100>;
    clock-frequency = <0>;
    interrupts = <0xA 0x8>;
    interrupt-parent = <&ipic>;
    };
};

In this example, the #address-cells and #size-cells properties of the soc node are both set to 1. This setting specifies that one cell is required to represent an address and one cell is required to represent the size of nodes that are children of this node.
The serial device reg property necessarily follows this specification set in the parent (soc) node—the address is represented by a single cell (0x4600), and the size is represented by a single cell (0x100).

舉例說明:

  • 1、如果node”soc”中”#address-cells=<1>”、”#size-cells=<1>”,那麼子node”serial”中”reg”屬性的解析爲“addr1 = 0x0, size1 = 0x100, addr2 = 0x0, size2 = 0x200”:
soc {
    #address-cells = <1>;
    #size-cells = <1>;
    serial {
        reg = <0x0 0x100 0x0 0x200>;
    }
}
  • 2、如果node”soc”中”#address-cells=<2>”、”#size-cells=<2>”,那麼子node”serial”中”reg”屬性的解析爲“addr1 = 0x100, size1 = 0x200”:
soc {
    #address-cells = <2>;
    #size-cells = <2>;
    serial {
        reg = <0x0 0x100 0x0 0x200>;
    }
}
  • 3、如果node”soc”中”#address-cells=<2>”、”#size-cells=<0>”,那麼子node”serial”中”reg”屬性的解析爲“addr1 = 0x100, addr2 = 0x200”:
soc {
    #address-cells = <2>;
    #size-cells = <0>;
    serial {
        reg = <0x0 0x100 0x0 0x200>;
    }
}

1.1.5、reg

  • “reg”屬性解析出”address,length”數字,解析格式依據父節點的”#address-cells、#size-cells”定義。
Property name: reg
Value type: <prop-encoded-array> encoded as an arbitrary number of (address, length) pairs.
Description: The reg property describes the address of the device’s resources within the address space defined by its parent bus. Most commonly this means the offsets and lengths of memory-mapped IO register blocks, but may have a different meaning on some bus types. Addresses in the address space defined by the root node are CPU real addresses.
The value is a <prop-encoded-array>, composed of an arbitrary number of pairs of address and length,
. The number of <u32> cells required to specify the address and length are bus-specific and are specified by the #address-cells and #size-cells properties in the parent of the device node. If the parent node specifies a value of 0 for #size-cells, the length field in the value of reg shall be omitted.
Example: Suppose a device within a system-on-a-chip had two blocks of registers, a 32-byte block at offset 0x3000 in the SOC and a 256-byte block at offset 0xFE00. The reg property would be encoded as follows (assuming #address-cells and #size-cells values of 1):

reg = <0x3000 0x20 0xFE00 0x100>;

1.1.6、ranges

  • “ranges”屬性用來做當前node和父node之間的地址映射,格式爲(child-bus-address, parentbus-address, length)。其中child-bus-address的解析長度受當前node的#address-cells屬性控制,parentbus-address的解析長度受父node的#address-cells屬性控制length的解析長度受當前node的#size-cells屬性控制。

Property name: ranges
Value type: <empty> or <prop-encoded-array> encoded as an arbitrary number of (child-bus-address, parentbus-address, length) triplets.
Description: The ranges property provides a means of defining a mapping or translation between the address space of the bus (the child address space) and the address space of the bus node’s parent (the parent address space).
The format of the value of the ranges property is an arbitrary number of triplets of (child-bus-address, parentbus-address, length)
• The child-bus-address is a physical address within the child bus’ address space. The number of cells to represent the address is bus dependent and can be determined from the #address-cells of this node (the node in which the ranges property appears).
• The parent-bus-address is a physical address within the parent bus’ address space. The number of cells to represent the parent address is bus dependent and can be determined from the #address-cells property of the node that defines the parent’s address space.
• The length specifies the size of the range in the child’s address space. The number of cells to represent the size can be determined from the #size-cells of this node (the node in which the ranges property appears).
If the property is defined with an <empty> value, it specifies that the parent and child address space is identical, and no address translation is required.
If the property is not present in a bus node, it is assumed that no mapping exists between children of the node and the parent address space.
Example: Address Translation Example:
soc {
compatible = “simple-bus”;
#address-cells = <1>;
#size-cells = <1>;
ranges = <0x0 0xe0000000 0x00100000>;
serial {
device_type = “serial”;
compatible = “ns16550”;
reg = <0x4600 0x100>;
clock-frequency = <0>;
interrupts = <0xA 0x8>;
interrupt-parent = <&ipic>;
};
};

The soc node specifies a ranges property of
<0x0 0xe0000000 0x00100000>;

This property value specifies that for an 1024KB range of address space, a child node addressed at physical 0x0 maps to a parent address of physical 0xe0000000. With this mapping, the serial device node can be addressed by a load or store at address 0xe0004600, an offset of 0x4600 (specified in reg) plus the 0xe0000000 mapping specified in ranges.

1.1.7、interrupt property

和中斷相關的node可以分成3種:

  • “Interrupt Generating Devices”,中斷髮生設備,這種設備可以發生中斷。
  • “Interrupt Controllers”,中斷控制器,處理中斷。
  • “Interrupt Nexus”,中斷聯結,路由中斷給中斷控制器。

這裏寫圖片描述

1.1.7.1、Interrupt Generating Devices Property

  • “interrupts”屬性用來定義設備的中斷解析,根據其”interrupt-parent”node中定義的“#interrupt-cells”來解析。比如#interrupt-cells=2,那根據2個cells爲單位來解析”interrupts”屬性。

Property name: interrupts
Value type: <prop-encoded-array> encoded as arbitrary number of interrupt specifiers
Description: The interrupts property of a device node defines the interrupt or interrupts that are generated by the device.
The value of the interrupts property consists of an arbitrary number of interrupt specifiers. The format of an interrupt specifier is defined by the binding of the interrupt domain root.
interrupts is overridden by the interrupts-extended property and normally only one or the other should be used.
Example: A common definition of an interrupt specifier in an open PIC–compatible interrupt domain consists of two cells; an interrupt number and level/sense information. See the following example, which defines a single interrupt specifier, with an interrupt number of 0xA and level/sense encoding of 8.

interrupts = <0xA 8>;
  • “interrupt-parent”屬性用來制定當前設備的Interrupt Controllers/Interrupt Nexus,phandle指向對應的node。
Property name: interrupt-parent
Value type: <phandle>
Description: Because the hierarchy of the nodes in the interrupt tree might not match the devicetree, the interrupt-parent property is available to make the definition of an interrupt parent explicit. The value is the phandle to the interrupt parent. If this property is missing from a device, its interrupt parent is assumed to be its devicetree parent.

1.1.7.2、Interrupt Controllers Property

  • “#interrupt-cells”屬性用來規定連接到該中斷控制器上的設備的”interrupts”屬性的解析長度。
Property name: #interrupt-cells
Value type: <u32>
Description: The #interrupt-cells property defines the number of cells required to encode an interrupt specifier for an interrupt domain.
  • “interrupt-controller”屬性用來聲明當前node爲中斷控制器。
Property name: interrupt-controller
Value type: <empty>
Description: The presence of an interrupt-controller property defines a node as an interrupt controller node.

1.1.7.3、Interrupt Nexus Property

  • “#interrupt-cells”屬性用來規定連接到該中斷控制器上的設備的”interrupts”屬性的解析長度。
Property name: #interrupt-cells
Value type: <u32>
Description: The #interrupt-cells property defines the number of cells required to encode an interrupt specifier for an interrupt domain.
  • “interrupt-map”屬性用來描述interrupt nexus設備對中斷的路由。解析格式爲5元素序列“child unit address, child interrupt specifier, interrupt-parent, parent unit address, parent interrupt specifier”。

其中:

“child unit address”的cells長度由子節點的“#address-cells”指定;
“child interrupt specifier”的cells長度由子節點的“#interrupt-cells”指定;
“interrupt-parent”phandle指向interrupt controller的引用;
“parent unit address”的cells長度由父節點的“#address-cells”指定;
“parent interrupt specifier”的cells長度由父節點的“#interrupt-cells”指定;

Property name: interrupt-map
Value type: <prop-encoded-array> encoded as an arbitrary number of interrupt mapping entries.
Description: An interrupt-map is a property on a nexus node that bridges one interrupt domain with a set of parent interrupt domains and specifies how interrupt specifiers in the child domain are mapped to their respective parent domains.
The interrupt map is a table where each row is a mapping entry consisting of five components: child unit address, child interrupt specifier, interrupt-parent, parent unit address, parent interrupt specifier.
child unit address The unit address of the child node being mapped. The number of 32-bit cells required to specify this is described by the #address-cells property of the bus node on which the child is located.
child interrupt specifier The interrupt specifier of the child node being mapped. The number of 32-bit cells required to specify this component is described by the #interrupt-cells property of this node—the nexus node containing the interrupt-map property.
interrupt-parent A single <phandle> value that points to the interrupt parent to which the child domain is being mapped.
parent unit address The unit address in the domain of the interrupt parent. The number of 32-bit cells required to specify this address is described by the #address-cells property of the node pointed to by the interrupt-parent field.
parent interrupt specifier The interrupt specifier in the parent domain. The number of 32-bit cells required to specify this component is described by the #interrupt-cells property of the node pointed to by the interrupt-parent field.
Lookups are performed on the interrupt mapping table by matching a unit-address/interrupt specifier pair against the child components in the interrupt-map. Because some fields in the unit interrupt specifier may not be relevant, a mask is applied before the lookup is done. This mask is defined in the interrupt-map-mask property (see section 2.4.3.2).

舉例:

soc {
    compatible = "simple-bus";
    #address-cells = <1>;
    #size-cells = <1>;
    open-pic {
        clock-frequency = <0>;
        interrupt-controller;
        #address-cells = <0>;
        #interrupt-cells = <2>;
    };
    pci {
        #interrupt-cells = <1>;
        #size-cells = <2>;
        #address-cells = <3>;
        interrupt-map-mask = <0xf800 0 0 7>;
        interrupt-map = <
        /* IDSEL 0x11 - PCI slot 1 */
        0x8800 0 0 1 &open-pic 2 1 /* INTA */
        0x8800 0 0 2 &open-pic 3 1 /* INTB */
        0x8800 0 0 3 &open-pic 4 1 /* INTC */
        0x8800 0 0 4 &open-pic 1 1 /* INTD */
        /* IDSEL 0x12 - PCI slot 2 */
        0x9000 0 0 1 &open-pic 3 1 /* INTA */
        0x9000 0 0 2 &open-pic 4 1 /* INTB */
        0x9000 0 0 3 &open-pic 1 1 /* INTC */
        0x9000 0 0 4 &open-pic 2 1 /* INTD */
        >;
    };
};
• For example, the first row of the interrupt-map table specifies the mapping for INTA of slot 1. The components of that row are shown here
    child unit address: 0x8800 0 0
    child interrupt specifier: 1
    interrupt parent: &open-pic
    parent unit address: (empty because #address-cells = <0> in the open-pic node)
    parent interrupt specifier: 2 1

1.2、標準Node

Node Name常常由兩部分組成“node-name@unit-address”,主要是爲了防止Node Name重複衝突:

  • “node-name”是node的名字;
  • “unit-address”是node中“reg”屬性描述的開始地址;

例如:”msdc@11240000”中node-name=“msdc”,unit-address=“11240000”。

/ {
    model = "mt6799";
    compatible = "mediatek,mt6799";
    interrupt-parent = <&gic>;
    #address-cells = <2>;
    #size-cells = <2>;

    msdc0:msdc@11240000 {
        compatible = "mediatek,msdc";
        reg = <0x0 0x11240000 0x0 0x10000
               0x0 0x10000e84 0x0 0x2>;    /* FPGA PWR_GPIO, PWR_GPIO_EO */
        interrupts = <GIC_SPI 79 IRQ_TYPE_LEVEL_LOW>;
    };

下面主要介紹一下一些預先定義的標準Node。

1.2.1、Root node

每個DeviceTree只有一個根節點。根節點需要有以下必備屬性:

Root Node Properties
Property Name Usage Value Type Definition
#address-cells R <u32> Specifies the number of <u32> cells to represent the address in the reg property in children of root.
#size-cells R <u32> Specifies the number of <u32> cells to represent the size in the reg property in children of root.
model R <string> Specifies a string that uniquely identifies the model of the system board. The recommended format is “manufacturer,model-number”.
compatible R <stringlist> Specifies a list of platform architectures with which this platform is compatible. This property can be used by operating systems in selecting platform specific code. The recommended form of the property value is:
“manufacturer,model”

For example:
compatible = “fsl,mpc8572ds”

1.2.2、/aliases node

用來給一些絕對路徑定義別名:

aliases {
    serial0 = "/simple-bus@fe000000/serial@llc500";
    ethernet0 = "/simple-bus@fe000000/ethernet@31c000";
};

1.2.3、/memory node

用來傳遞內存佈局:

/memory Node Properties
Property Name Usage Value Type Definition
device_type R <string> Value shall be “memory”
reg R <prop-encoded-array> Consists of an arbitrary number of address and size pairs that specify the physical address and size of the memory ranges.
initial-mapped-area O <prop-encoded-array> Specifies the address and size of the Initial Mapped Area Is a prop-encoded-array consisting of a triplet of (effective address, physical address, size). The effective and physical address shall each be 64-bit (<u64> value), and the size shall be 32-bits (<u32> value).

舉例:

• RAM: starting address 0x0, length 0x80000000 (2GB)
• RAM: starting address 0x100000000, length 0x100000000 (4GB)
\ {
    #address-cells = <2>;
    #size-cells = <2>;

    memory@0 {
        device_type = "memory";
        reg = <0x000000000 0x00000000 0x00000000 0x80000000
        0x000000001 0x00000000 0x00000001 0x00000000>;
    };
}

1.2.4、/chosen node

其中“bootargs”屬性用來傳遞cmdline參數,“stdout-path”屬性用來指定標準輸出設備,“stdin-path”屬性用來指定標準輸入設備。

/chosen Node Properties
Property Name Usage Value Type Definition
bootargs O <string> A string that specifies the boot arguments for the client program. The value could potentially be a null string if no boot arguments are required.
stdout-path O <string> A string that specifies the full path to the node representing the device to be used for boot console output. If the character “:” is present in the value it terminates the path. The value may be an alias. If the stdin-path property is not specified, stdout-path should be assumed to define the input device.
stdin-path O <string> A string that specifies the full path to the node representing the device to be used for boot console input. If the character “:” is present in the value it terminates the path. The value may be an alias.

舉例:

    /* chosen */
    chosen {
        bootargs = "console=tty0 console=ttyMT0,921600n1 root=/dev/ram";
    };

1.2.5、/cpus node

/cpus節點也是必須的,下面舉個具體例子:

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

        cpu0: cpu@0 {
            device_type = "cpu";
            compatible = "arm,cortex-a35";
            reg = <0x000>;
            enable-method = "psci";
            cpu-idle-states = <&LEGACY_MCDI &LEGACY_SODI &LEGACY_SODI3 &LEGACY_DPIDLE>,
                      <&LEGACY_SUSPEND &MCDI &SODI &SODI3 &DPIDLE &SUSPEND>;
            cpu-release-addr = <0x0 0x40000200>;
            clock-frequency = <1248000000>;
        };

        cpu1: cpu@001 {
            device_type = "cpu";
            compatible = "arm,cortex-a35";
            reg = <0x001>;
            enable-method = "psci";
            cpu-idle-states = <&LEGACY_MCDI &LEGACY_SODI &LEGACY_SODI3 &LEGACY_DPIDLE>,
                      <&LEGACY_SUSPEND &MCDI &SODI &SODI3 &DPIDLE &SUSPEND>;
            cpu-release-addr = <0x0 0x40000200>;
            clock-frequency = <1248000000>;
        };

        cpu2: cpu@002 {
            device_type = "cpu";
            compatible = "arm,cortex-a35";
            reg = <0x002>;
            enable-method = "psci";
            cpu-idle-states = <&LEGACY_MCDI &LEGACY_SODI &LEGACY_SODI3 &LEGACY_DPIDLE>,
                      <&LEGACY_SUSPEND &MCDI &SODI &SODI3 &DPIDLE &SUSPEND>;
            cpu-release-addr = <0x0 0x40000200>;
            clock-frequency = <1248000000>;
        };

        cpu3: cpu@003 {
            device_type = "cpu";
            compatible = "arm,cortex-a35";
            reg = <0x003>;
            enable-method = "psci";
            cpu-idle-states = <&LEGACY_MCDI &LEGACY_SODI &LEGACY_SODI3 &LEGACY_DPIDLE>,
                      <&LEGACY_SUSPEND &MCDI &SODI &SODI3 &DPIDLE &SUSPEND>;
            cpu-release-addr = <0x0 0x40000200>;
            clock-frequency = <1248000000>;
        };

        cpu4: cpu@100 {
            device_type = "cpu";
            compatible = "arm,cortex-a53";
            reg = <0x100>;
            enable-method = "psci";
            cpu-idle-states = <&LEGACY_MCDI &LEGACY_SODI &LEGACY_SODI3 &LEGACY_DPIDLE>,
                      <&LEGACY_SUSPEND &MCDI &SODI &SODI3 &DPIDLE &SUSPEND>;
            cpu-release-addr = <0x0 0x40000200>;
            clock-frequency = <1378000000>;
        };

        cpu5: cpu@101 {
            device_type = "cpu";
            compatible = "arm,cortex-a53";
            reg = <0x101>;
            enable-method = "psci";
            cpu-idle-states = <&LEGACY_MCDI &LEGACY_SODI &LEGACY_SODI3 &LEGACY_DPIDLE>,
                      <&LEGACY_SUSPEND &MCDI &SODI &SODI3 &DPIDLE &SUSPEND>;
            cpu-release-addr = <0x0 0x40000200>;
            clock-frequency = <1378000000>;
        };

        cpu6: cpu@102 {
            device_type = "cpu";
            compatible = "arm,cortex-a53";
            reg = <0x102>;
            enable-method = "psci";
            cpu-idle-states = <&LEGACY_MCDI &LEGACY_SODI &LEGACY_SODI3 &LEGACY_DPIDLE>,
                      <&LEGACY_SUSPEND &MCDI &SODI &SODI3 &DPIDLE &SUSPEND>;
            cpu-release-addr = <0x0 0x40000200>;
            clock-frequency = <1378000000>;
        };

        cpu7: cpu@103 {
            device_type = "cpu";
            compatible = "arm,cortex-a53";
            reg = <0x103>;
            enable-method = "psci";
            cpu-idle-states = <&LEGACY_MCDI &LEGACY_SODI &LEGACY_SODI3 &LEGACY_DPIDLE>,
                      <&LEGACY_SUSPEND &MCDI &SODI &SODI3 &DPIDLE &SUSPEND>;
            cpu-release-addr = <0x0 0x40000200>;
            clock-frequency = <1378000000>;
        };

        cpu8: cpu@200 {
            device_type = "cpu";
            compatible = "arm,cortex-a73";
            reg = <0x200>;
            enable-method = "psci";
            cpu-idle-states = <&LEGACY_MCDI &LEGACY_SODI &LEGACY_SODI3 &LEGACY_DPIDLE>,
                      <&LEGACY_SUSPEND &MCDI &SODI &SODI3 &DPIDLE &SUSPEND>;
            cpu-release-addr = <0x0 0x40000200>;
            clock-frequency = <1638000000>;
        };

        cpu9: cpu@201 {
            device_type = "cpu";
            compatible = "arm,cortex-a73";
            reg = <0x201>;
            enable-method = "psci";
            cpu-idle-states = <&LEGACY_MCDI &LEGACY_SODI &LEGACY_SODI3 &LEGACY_DPIDLE>,
                      <&LEGACY_SUSPEND &MCDI &SODI &SODI3 &DPIDLE &SUSPEND>;
            cpu-release-addr = <0x0 0x40000200>;
            clock-frequency = <1638000000>;
        };

        cpu-map {
            cluster0 {
                core0 {
                    cpu = <&cpu0>;
                };


                core1 {
                    cpu = <&cpu1>;
                };

                core2 {
                    cpu = <&cpu2>;
                };

                core3 {
                    cpu = <&cpu3>;
                };

            };

            cluster1 {
                core0 {
                    cpu = <&cpu4>;
                };

                core1 {
                    cpu = <&cpu5>;
                };

                core2 {
                    cpu = <&cpu6>;
                };

                core3 {
                    cpu = <&cpu7>;
                };

            };

            cluster2 {
                core0 {
                    cpu = <&cpu8>;
                };

                core1 {
                    cpu = <&cpu9>;
                };

            };
        };

        idle-states {
            entry-method = "arm,psci";

            LEGACY_MCDI: legacy-mcdi {
                compatible = "arm,idle-state";
                arm,psci-suspend-param = <0x0000001>;
                entry-latency-us = <600>;
                exit-latency-us = <600>;
                min-residency-us = <1200>;
            };

            LEGACY_SODI: legacy-sodi {
                compatible = "arm,idle-state";
                arm,psci-suspend-param = <0x0000002>;
                entry-latency-us = <600>;
                exit-latency-us = <600>;
                min-residency-us = <1200>;
            };

            LEGACY_SODI3: legacy-sodi3 {
                compatible = "arm,idle-state";
                arm,psci-suspend-param = <0x0000003>;
                entry-latency-us = <600>;
                exit-latency-us = <600>;
                min-residency-us = <1200>;
            };

            LEGACY_DPIDLE: legacy-dpidle {
                compatible = "arm,idle-state";
                arm,psci-suspend-param = <0x0000004>;
                entry-latency-us = <600>;
                exit-latency-us = <600>;
                min-residency-us = <1200>;
            };

            LEGACY_SUSPEND: legacy-suspend {
                compatible = "arm,idle-state";
                arm,psci-suspend-param = <0x0000005>;
                entry-latency-us = <600>;
                exit-latency-us = <600>;
                min-residency-us = <1200>;
            };

            MCDI: mcdi {
                compatible = "arm,idle-state";
                arm,psci-suspend-param = <0x0010001>;
                entry-latency-us = <600>;
                exit-latency-us = <600>;
                min-residency-us = <1200>;
            };

            SODI: sodi {
                compatible = "arm,idle-state";
                arm,psci-suspend-param = <0x1010002>;
                entry-latency-us = <800>;
                exit-latency-us = <1000>;
                min-residency-us = <2000>;
            };

            SODI3: sodi3 {
                compatible = "arm,idle-state";
                arm,psci-suspend-param = <0x1010003>;
                entry-latency-us = <800>;
                exit-latency-us = <1000>;
                min-residency-us = <2000>;
            };

            DPIDLE: dpidle {
                compatible = "arm,idle-state";
                arm,psci-suspend-param = <0x1010004>;
                entry-latency-us = <800>;
                exit-latency-us = <1000>;
                min-residency-us = <2000>;
            };

            SUSPEND: suspend {
                compatible = "arm,idle-state";
                arm,psci-suspend-param = <0x1010005>;
                entry-latency-us = <800>;
                exit-latency-us = <1000>;
                min-residency-us = <2000>;
            };

        };
    };

2、DTB

2.1、DTB的編譯

DTB(Devicetree Blob)是DTS的二進制文件格式,Kernel使用DTC工具將DTS源文件編譯成DTB,bootloader再將DTB文件傳遞給Kernel解析。

不遵守標準書寫的DTS文件在編譯的時候會報錯。

2.2、DTB的文件結構

這裏寫圖片描述

DTB文件的結構如上圖所示,主要在3部分:

  • struct ftd_header。文件頭結構;
  • structure block。存放含Node和Property的Value;
  • strings block。存放Property的Name;把Property Name單獨分爲一個區域的原因是,有很多Property Name是重複的,單獨一個區域可以使用指針引用,節約空間。

dtb中的fdt_header的數據結構:

struct fdt_header {
    uint32_t magic;
    uint32_t totalsize;
    uint32_t off_dt_struct;
    uint32_t off_dt_strings;
    uint32_t off_mem_rsvmap;
    uint32_t version;
    uint32_t last_comp_version;
    uint32_t boot_cpuid_phys;
    uint32_t size_dt_strings;
    uint32_t size_dt_struct;
};

dtb中node header的數據結構:

struct fdt_node_header {
    fdt32_t tag;
    char name[0];   // node name 存放在structure block
};

dtb中property header的數據結構:

struct fdt_property {
    fdt32_t tag;
    fdt32_t len;
    fdt32_t nameoff;    // perperty name存放在strings block
    char data[0];
};

整個文件使用5種token來分割出node和property:

  • FDT_BEGIN_NODE (0x00000001)
  • FDT_END_NODE (0x00000002)
  • FDT_PROP (0x00000003)
  • FDT_NOP (0x00000004)
  • FDT_END (0x00000009)

可以使用hex編輯器來查看DTB文件的結構:

這裏寫圖片描述

2.3、Bootloader對DTB的傳遞

沒有仔細去看

3、Kernel解析

3.1、DTB解析

3.1.1 setup_machine_fdt()

直接在dtb中解析根節點的一些屬性和子節點給系統早期使用。

  • 解析”/”節點的model”屬性給machine_desc賦值;
  • 解析”/chosen”node中的”bootargs”屬性給boot_command_line;
  • 解析”/”節點的”#size-cells”、”#address-cells”屬性;
  • 解析”/memory”node中的”reg”屬性,並將memory區域加入到系統;
start_kernel() -> setup_arch() -> setup_machine_fdt():
↓

static void __init setup_machine_fdt(phys_addr_t dt_phys)
{
    /* (1) 映射dtb內存,到使之可以訪問 */
    void *dt_virt = fixmap_remap_fdt(dt_phys);

    /* (2) 早期掃描device tree中的一些node和property */
    if (!dt_virt || !early_init_dt_scan(dt_virt)) {
        pr_crit("\n"
            "Error: invalid device tree blob at physical address %pa (virtual address 0x%p)\n"
            "The dtb must be 8-byte aligned and must not exceed 2 MB in size\n"
            "\nPlease check your bootloader.",
            &dt_phys, dt_virt);

        while (true)
            cpu_relax();
    }

    /* (3) 使用device tree中root node的"model/compatible"屬性給machine_desc賦值 */
    machine_desc_set(of_flat_dt_get_machine_name());
    dump_stack_set_arch_desc("%s (DT)", of_flat_dt_get_machine_name());
}
↓

bool __init early_init_dt_scan(void *params)
{
    bool status;

    /* (2.1)校驗dtb數據 */
    status = early_init_dt_verify(params);
    if (!status)
        return false;

    /* (2.2) */
    early_init_dt_scan_nodes();
    return true;
}
↓

void __init early_init_dt_scan_nodes(void)
{
    /* (2.2.1) 解析"/chosen"node中的"bootargs"屬性 */
    /* Retrieve various information from the /chosen node */
    of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);

    /* (2.2.2) 解析"/"node中的"#size-cells"、"#address-cells"屬性 */
    /* Initialize {size,address}-cells info */
    of_scan_flat_dt(early_init_dt_scan_root, NULL);

    /* (2.2.3) 解析"/memory"node中的"reg"屬性,並將memory區域加入到系統 */
    /* Setup memory, calling early_init_dt_add_memory_arch */
    of_scan_flat_dt(early_init_dt_scan_memory, NULL);
}
↓

early_init_dt_scan_memory() -> early_init_dt_add_memory_arch() -> memblock_add()

3.1.2 unflatten_device_tree()

將DTB完全解析爲內核使用的的device_node、property結構:

start_kernel() -> setup_arch() -> unflatten_device_tree():
↓

void __init unflatten_device_tree(void)
{
    /* (1) 解析dtb數據到kernel中 */
    __unflatten_device_tree(initial_boot_params, &of_root,
                early_init_dt_alloc_memory_arch);

    /* (2) 掃描"/aliases"、"/chosen"節點來進行一些預製值的配置 */
    /* Get pointer to "/chosen" and "/aliases" nodes for use everywhere */
    of_alias_scan(early_init_dt_alloc_memory_arch);
}
↓

static void __unflatten_device_tree(const void *blob,
                 struct device_node **mynodes,
                 void * (*dt_alloc)(u64 size, u64 align))
{
    unsigned long size;
    int start;
    void *mem;

    pr_debug(" -> unflatten_device_tree()\n");

    if (!blob) {
        pr_debug("No device tree pointer\n");
        return;
    }

    pr_debug("Unflattening device tree:\n");
    pr_debug("magic: %08x\n", fdt_magic(blob));
    pr_debug("size: %08x\n", fdt_totalsize(blob));
    pr_debug("version: %08x\n", fdt_version(blob));

    if (fdt_check_header(blob)) {
        pr_err("Invalid device tree blob header\n");
        return;
    }

    /* (1.1) 第一遍掃描,計算dtb解析需要的內存空間 */
    /* First pass, scan for size */
    start = 0;
    size = (unsigned long)unflatten_dt_node(blob, NULL, &start, NULL, NULL, 0, true);
    size = ALIGN(size, 4);

    pr_debug("  size is %lx, allocating...\n", size);

    /* (1.2) 分配所需內存 */
    /* Allocate memory for the expanded device tree */
    mem = dt_alloc(size + 4, __alignof__(struct device_node));
    memset(mem, 0, size);

    *(__be32 *)(mem + size) = cpu_to_be32(0xdeadbeef);

    pr_debug("  unflattening %p...\n", mem);

    /* (1.3)第二遍掃描,在分配的內存中創建device_node、property樹形結構來存儲dtb的解析 */
    /* Second pass, do actual unflattening */
    start = 0;
    unflatten_dt_node(blob, mem, &start, NULL, mynodes, 0, false);
    if (be32_to_cpup(mem + size) != 0xdeadbeef)
        pr_warning("End of tree marker overwritten: %08x\n",
               be32_to_cpup(mem + size));

    pr_debug(" <- unflatten_device_tree()\n");
}
↓

static void * unflatten_dt_node(const void *blob,
                void *mem,
                int *poffset,
                struct device_node *dad,
                struct device_node **nodepp,
                unsigned long fpsize,
                bool dryrun)
{
    const __be32 *p;
    struct device_node *np;
    struct property *pp, **prev_pp = NULL;
    const char *pathp;
    unsigned int l, allocl;
    static int depth;
    int old_depth;
    int offset;
    int has_name = 0;
    int new_format = 0;

    /* (1.1.1) 解析node,解析node中的name值 */
    pathp = fdt_get_name(blob, *poffset, &l);
    if (!pathp)
        return mem;

    allocl = ++l;   /* l 爲當前路徑的長度 */

    /* version 0x10 has a more compact unit name here instead of the full
     * path. we accumulate the full path size using "fpsize", we'll rebuild
     * it later. We detect this because the first character of the name is
     * not '/'.
     */
    if ((*pathp) != '/') {
        new_format = 1;
        if (fpsize == 0) {
            /* root node: special case. fpsize accounts for path
             * plus terminating zero. root node only has '/', so
             * fpsize should be 2, but we want to avoid the first
             * level nodes to have two '/' so we use fpsize 1 here
             */
            fpsize = 1;
            allocl = 2;
            l = 1;
            pathp = "";
        } else {
            /* account for '/' and path size minus terminal 0
             * already in 'l'
             */
            fpsize += l;    /* 當前full path的長度 = 上一次full path的長度 + 當前node nam的長度 l */
            allocl = fpsize;
        }
    }

    /* 計算解析當前node節點需要的內存大小 = device_node + full path  */
    np = unflatten_dt_alloc(&mem, sizeof(struct device_node) + allocl,
                __alignof__(struct device_node));
    /*  dryrun = true,只進行長度計算
     dryrun = fasle,進行實際的賦值  */
    if (!dryrun) {
        char *fn;
        of_node_init(np);
        np->full_name = fn = ((char *)np) + sizeof(*np);     /* device_node->full_name,指向device_node結構體的結尾 */ 
        if (new_format) {
            /* rebuild full path for new format */
            if (dad && dad->parent) {
                 /* 先拷入上次的full name */
                strcpy(fn, dad->full_name);
#ifdef DEBUG
                if ((strlen(fn) + l + 1) != allocl) {
                    pr_debug("%s: p: %d, l: %d, a: %d\n",
                        pathp, (int)strlen(fn),
                        l, allocl);
                }
#endif
                fn += strlen(fn);
            }
            /* 再加上 '/' */
            *(fn++) = '/';
        }
        /* 最後加上當前node的name */
        memcpy(fn, pathp, l);

        prev_pp = &np->properties;
        /* node和node之間樹形結構的創建 */
        if (dad != NULL) {  
            np->parent = dad;
            np->sibling = dad->child;
            dad->child = np;
        }
    }

    /* (1.1.2) 解析node中的property */
    /* process properties */
    for (offset = fdt_first_property_offset(blob, *poffset);
         (offset >= 0);
         (offset = fdt_next_property_offset(blob, offset))) {
        const char *pname;
        u32 sz;

        /* 解析一個property:
          p:property中的data
          pname:property的name指針,實際存儲位置在dt_strings區域中
         sz:property data的長度
        */
        if (!(p = fdt_getprop_by_offset(blob, offset, &pname, &sz))) {
            offset = -FDT_ERR_INTERNAL;
            break;
        }

        if (pname == NULL) {
            pr_info("Can't find property name in list !\n");
            break;
        }
        if (strcmp(pname, "name") == 0)
            has_name = 1;
        /* 計算解析當前property的內存大小 = property */
        pp = unflatten_dt_alloc(&mem, sizeof(struct property),
                    __alignof__(struct property));
        /* 實際的property賦值 */
        if (!dryrun) {
            /* We accept flattened tree phandles either in
             * ePAPR-style "phandle" properties, or the
             * legacy "linux,phandle" properties.  If both
             * appear and have different values, things
             * will get weird.  Don't do that. */
            /* 如果property爲"phandle",設置父node的device_node->phandle爲當前屬性的值 */
            if ((strcmp(pname, "phandle") == 0) ||
                (strcmp(pname, "linux,phandle") == 0)) {
                if (np->phandle == 0)
                    np->phandle = be32_to_cpup(p);
            }
            /* And we process the "ibm,phandle" property
             * used in pSeries dynamic device tree
             * stuff */
            if (strcmp(pname, "ibm,phandle") == 0)
                np->phandle = be32_to_cpup(p);
            /* 給property的其他字段賦值:(DTB的空間沒有釋放,被property成員指針引用)
            property->name:指針指向dtb strings blcok區域中的屬性name
            property->length:屬性data的長度
            property->value:指針指向dtb stucture block區域中的屬性data
             */
            pp->name = (char *)pname;
            pp->length = sz;
            pp->value = (__be32 *)p;
            *prev_pp = pp;
            prev_pp = &pp->next;
        }
    }
    /* with version 0x10 we may not have the name property, recreate
     * it here from the unit name if absent
     */
    if (!has_name) {
        const char *p1 = pathp, *ps = pathp, *pa = NULL;
        int sz;

        while (*p1) {
            if ((*p1) == '@')
                pa = p1;
            if ((*p1) == '/')
                ps = p1 + 1;
            p1++;
        }
        if (pa < ps)
            pa = p1;
        sz = (pa - ps) + 1;
        pp = unflatten_dt_alloc(&mem, sizeof(struct property) + sz,
                    __alignof__(struct property));
        if (!dryrun) {
            pp->name = "name";
            pp->length = sz;
            pp->value = pp + 1;
            *prev_pp = pp;
            prev_pp = &pp->next;
            memcpy(pp->value, ps, sz - 1);
            ((char *)pp->value)[sz - 1] = 0;
            pr_debug("fixed up name for %s -> %s\n", pathp,
                (char *)pp->value);
        }
    }
    /* 根據"name"、 "device_type"屬性,來給device_node結構中的name、type成員賦值 */
    if (!dryrun) {
        *prev_pp = NULL;
        np->name = of_get_property(np, "name", NULL);
        np->type = of_get_property(np, "device_type", NULL);

        if (!np->name)
            np->name = "<NULL>";
        if (!np->type)
            np->type = "<NULL>";
    }

    /* (1.1.3)如果還有子node的存在,遞歸解析 */
    old_depth = depth;
    *poffset = fdt_next_node(blob, *poffset, &depth);
    if (depth < 0)
        depth = 0;
    while (*poffset > 0 && depth > old_depth)
        mem = unflatten_dt_node(blob, mem, poffset, np, NULL,
                    fpsize, dryrun);

    if (*poffset < 0 && *poffset != -FDT_ERR_NOTFOUND)
        pr_err("unflatten: error %d processing FDT\n", *poffset);

    /*
     * Reverse the child list. Some drivers assumes node order matches .dts
     * node order
     */
    if (!dryrun && np->child) {
        struct device_node *child = np->child;
        np->child = NULL;
        while (child) {
            struct device_node *next = child->sibling;
            child->sibling = np->child;
            np->child = child;
            child = next;
        }
    }

    if (nodepp)
        *nodepp = np;

    return mem;
}

3.2、Device創建

3.2.1 of_platform_populate()

首先root節點下的第1級子節點創建成platform device。

  • 對root節點下的第1級子節點,如果有”compatible”屬性創建對應platform device;
  • 如果”compatible”屬性等於of_default_bus_match_table(“simple-bus”/”simple-mfd”/”arm,amba-bus”)中任意一種,繼續對其子節點進行platform device創建。
start_kernel() -> ... ->do_initcalls() -> arm64_device_init():
↓

const struct of_device_id of_default_bus_match_table[] = {
    { .compatible = "simple-bus", },
    { .compatible = "simple-mfd", },
#ifdef CONFIG_ARM_AMBA
    { .compatible = "arm,amba-bus", },
#endif /* CONFIG_ARM_AMBA */
    {} /* Empty terminated list */
};

static int __init arm64_device_init(void)
{
    if (of_have_populated_dt()) {
        of_iommu_init();
        of_platform_populate(NULL, of_default_bus_match_table,
                     NULL, NULL);
    } else if (acpi_disabled) {
        pr_crit("Device tree not populated\n");
    }
    return 0;
}
↓

int of_platform_populate(struct device_node *root,
            const struct of_device_id *matches,
            const struct of_dev_auxdata *lookup,
            struct device *parent)
{
    struct device_node *child;
    int rc = 0;

    /* (1) 獲取dts中的root node */
    root = root ? of_node_get(root) : of_find_node_by_path("/");
    if (!root)
        return -EINVAL;

    /* (2) 對root node的child node進行platform device創建 */
    for_each_child_of_node(root, child) {
        rc = of_platform_bus_create(child, matches, lookup, parent, true);
        if (rc) {
            of_node_put(child);
            break;
        }
    }
    of_node_set_flag(root, OF_POPULATED_BUS);

    of_node_put(root);
    return rc;
}
↓

static int of_platform_bus_create(struct device_node *bus,
                  const struct of_device_id *matches,
                  const struct of_dev_auxdata *lookup,
                  struct device *parent, bool strict)
{
    const struct of_dev_auxdata *auxdata;
    struct device_node *child;
    struct platform_device *dev;
    const char *bus_id = NULL;
    void *platform_data = NULL;
    int rc = 0;

    /* (2.1) 確保要創建爲platform device的node,擁有"compatible"屬性 */
    /* Make sure it has a compatible property */
    if (strict && (!of_get_property(bus, "compatible", NULL))) {
        pr_debug("%s() - skipping %s, no compatible prop\n",
             __func__, bus->full_name);
        return 0;
    }

    auxdata = of_dev_lookup(lookup, bus);
    if (auxdata) {
        bus_id = auxdata->name;
        platform_data = auxdata->platform_data;
    }

    if (of_device_is_compatible(bus, "arm,primecell")) {
        /*
         * Don't return an error here to keep compatibility with older
         * device tree files.
         */
        of_amba_device_create(bus, bus_id, platform_data, parent);
        return 0;
    }

    /* (2.2) 對當前的node創建platform device */
    dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent);
    /* (2.3) 根據of_default_bus_match_table,如果node中含有以下屬性:
    compatible = "simple-bus"
    compatible = "simple-mfd"
    compatible = "arm,amba-bus"
    則繼續對node的子node進行platform device創建
     */
    if (!dev || !of_match_node(matches, bus))
        return 0;

    /* (2.4) 遞歸對本node的child node進行platform device創建 */
    for_each_child_of_node(bus, child) {
        pr_debug("   create child: %s\n", child->full_name);
        rc = of_platform_bus_create(child, matches, lookup, &dev->dev, strict);
        if (rc) {
            of_node_put(child);
            break;
        }
    }
    of_node_set_flag(bus, OF_POPULATED_BUS);
    return rc;
}
↓

static struct platform_device *of_platform_device_create_pdata(
                    struct device_node *np,
                    const char *bus_id,
                    void *platform_data,
                    struct device *parent)
{
    struct platform_device *dev;

    if (!of_device_is_available(np) ||
        of_node_test_and_set_flag(np, OF_POPULATED))
        return NULL;

    /* (2.2.1) 分配node對應的platform_device結構,
     並且解析node中的"reg"、"interrupts"屬性,
    作爲platform_device->resource */
    dev = of_device_alloc(np, bus_id, parent);
    if (!dev)
        goto err_clear_flag;

    /* (2.2.2) device對應的bus爲platform_bus_type  */
    dev->dev.bus = &platform_bus_type;
    dev->dev.platform_data = platform_data;
    of_dma_configure(&dev->dev, dev->dev.of_node);
    of_msi_configure(&dev->dev, dev->dev.of_node);

    /* (2.2.3) 註冊platform_device->dev爲標準的device */
    if (of_device_add(dev) != 0) {
        of_dma_deconfigure(&dev->dev);
        platform_device_put(dev);
        goto err_clear_flag;
    }

    return dev;

err_clear_flag:
    of_node_clear_flag(np, OF_POPULATED);
    return NULL;
}
↓

struct platform_device *of_device_alloc(struct device_node *np,
                  const char *bus_id,
                  struct device *parent)
{
    struct platform_device *dev;
    int rc, i, num_reg = 0, num_irq;
    struct resource *res, temp_res;

    /* (2.2.1.1) 分配platform_device空間  */
    dev = platform_device_alloc("", -1);
    if (!dev)
        return NULL;

    /* (2.2.1.2) 計算node中"reg"屬性中"address、size"resource的個數  */
    /* count the io and irq resources */
    while (of_address_to_resource(np, num_reg, &temp_res) == 0)
        num_reg++;
    num_irq = of_irq_count(np); /* 計算node中"interrupts"屬性中irq的個數 */

    /* (2.2.1.3) 給resource分配空間並解析值 */
    /* Populate the resource table */
    if (num_irq || num_reg) {
        res = kzalloc(sizeof(*res) * (num_irq + num_reg), GFP_KERNEL);
        if (!res) {
            platform_device_put(dev);
            return NULL;
        }

        dev->num_resources = num_reg + num_irq;
        dev->resource = res;
        for (i = 0; i < num_reg; i++, res++) {
            rc = of_address_to_resource(np, i, res);
            WARN_ON(rc);
        }
        if (of_irq_to_resource_table(np, res, num_irq) != num_irq)
            pr_debug("not all legacy IRQ resources mapped for %s\n",
                 np->name);
    }

    /* (2.2.1.4) 根據device能夠找到of node  */
    dev->dev.of_node = of_node_get(np);
    dev->dev.parent = parent ? : &platform_bus;

    /* (2.2.1.5) 配置device的那麼,基本命名規則爲:
    dev_set_name(dev, dev_name(dev) ? "%llx.%s:%s" : "%llx.%s",
                         (unsigned long long)addr, node->name,
                         dev_name(dev))
    */
    if (bus_id)
        dev_set_name(&dev->dev, "%s", bus_id);
    else
        of_device_make_bus_id(&dev->dev);

    return dev;
}

3.2.2 mt_i2c_driver

因爲第1級子節點會被註冊成platform device,例如i2c/spi控制器,那麼對應也需要註冊platform driver。已i2c控制器驅動爲例:

  • 控制器首先會創建對應platform driver,把adapter註冊成i2c device;
  • 在adapter的probe過程中,會調用of_i2c_register_devices()函數遍歷控制器下掛的i2c設備的DTS節點,並將其註冊成i2c_client;
drivers\i2c\busses\i2c-mtk.c:

static const struct of_device_id mtk_i2c_of_match[] = {
    { .compatible = "mediatek,mt6735-i2c", .data = &mt6735_compat },
    { .compatible = "mediatek,mt6797-i2c", .data = &mt6797_compat },
    { .compatible = "mediatek,mt6757-i2c", .data = &mt6757_compat },
    { .compatible = "mediatek,mt6799-i2c", .data = &mt6799_compat },
    { .compatible = "mediatek,elbrus-i2c", .data = &elbrus_compat },
    {},
};

static struct platform_driver mt_i2c_driver = {
    .probe = mt_i2c_probe,
    .remove = mt_i2c_remove,
    .driver = {
        .name = I2C_DRV_NAME,
        .owner = THIS_MODULE,
        .of_match_table = of_match_ptr(mtk_i2c_of_match),
    },
};

static int mt_i2c_probe(struct platform_device *pdev)
{
    int ret = 0;
    struct mt_i2c *i2c;
    unsigned int clk_src_in_hz;
    struct resource *res;
    const struct of_device_id *of_id;

    i2c = devm_kzalloc(&pdev->dev, sizeof(struct mt_i2c), GFP_KERNEL);
    if (i2c == NULL)
        return -ENOMEM;

    ret = mt_i2c_parse_dt(pdev->dev.of_node, i2c);
    if (ret)
        return -EINVAL;

    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);

    i2c->base = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(i2c->base))
        return PTR_ERR(i2c->base);

    res = platform_get_resource(pdev, IORESOURCE_MEM, 1);

    i2c->pdmabase = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(i2c->pdmabase))
        return PTR_ERR(i2c->pdmabase);

    i2c->irqnr = platform_get_irq(pdev, 0);
    if (i2c->irqnr <= 0)
        return -EINVAL;
    init_waitqueue_head(&i2c->wait);

    ret = devm_request_irq(&pdev->dev, i2c->irqnr, mt_i2c_irq,
#ifdef CONFIG_MEIZU_BSP
        IRQF_NO_SUSPEND | IRQF_TRIGGER_NONE, I2C_DRV_NAME, i2c);
#else
        IRQF_TRIGGER_NONE, I2C_DRV_NAME, i2c);
#endif /*CONFIG_MEIZU_BSP*/
    if (ret < 0) {
        dev_err(&pdev->dev,
            "Request I2C IRQ %d fail\n", i2c->irqnr);
        return ret;
    }
    of_id = of_match_node(mtk_i2c_of_match, pdev->dev.of_node);
    if (!of_id)
        return -EINVAL;

    i2c->dev_comp = of_id->data;
    i2c->adap.dev.of_node = pdev->dev.of_node;
    i2c->dev = &i2c->adap.dev;
    i2c->adap.dev.parent = &pdev->dev;
    i2c->adap.owner = THIS_MODULE;
    i2c->adap.algo = &mt_i2c_algorithm;
    i2c->adap.algo_data = NULL;
    i2c->adap.timeout = 2 * HZ;
    i2c->adap.retries = 1;
    i2c->adap.nr = i2c->id;
    spin_lock_init(&i2c->cg_lock);

    if (i2c->dev_comp->dma_support == 2) {
        if (dma_set_mask(&pdev->dev, DMA_BIT_MASK(33))) {
            dev_err(&pdev->dev, "dma_set_mask return error.\n");
            return -EINVAL;
        }
    } else if (i2c->dev_comp->dma_support == 3) {
        if (dma_set_mask(&pdev->dev, DMA_BIT_MASK(36))) {
            dev_err(&pdev->dev, "dma_set_mask return error.\n");
            return -EINVAL;
        }
    }

#if !defined(CONFIG_MT_I2C_FPGA_ENABLE)
    i2c->clk_main = devm_clk_get(&pdev->dev, "main");
    if (IS_ERR(i2c->clk_main)) {
        dev_err(&pdev->dev, "cannot get main clock\n");
        return PTR_ERR(i2c->clk_main);
    }
    i2c->clk_dma = devm_clk_get(&pdev->dev, "dma");
    if (IS_ERR(i2c->clk_dma)) {
        dev_err(&pdev->dev, "cannot get dma clock\n");
        return PTR_ERR(i2c->clk_dma);
    }
    i2c->clk_arb = devm_clk_get(&pdev->dev, "arb");
    if (IS_ERR(i2c->clk_arb))
        i2c->clk_arb = NULL;
    else
        dev_dbg(&pdev->dev, "i2c%d has the relevant arbitrator clk.\n", i2c->id);
#endif

    if (i2c->have_pmic) {
        i2c->clk_pmic = devm_clk_get(&pdev->dev, "pmic");
        if (IS_ERR(i2c->clk_pmic)) {
            dev_err(&pdev->dev, "cannot get pmic clock\n");
            return PTR_ERR(i2c->clk_pmic);
        }
        clk_src_in_hz = clk_get_rate(i2c->clk_pmic) / i2c->clk_src_div;
    } else {
        clk_src_in_hz = clk_get_rate(i2c->clk_main) / i2c->clk_src_div;
    }
    dev_dbg(&pdev->dev, "clock source %p,clock src frequency %d\n",
        i2c->clk_main, clk_src_in_hz);

    strlcpy(i2c->adap.name, I2C_DRV_NAME, sizeof(i2c->adap.name));
    mutex_init(&i2c->i2c_mutex);
    ret = i2c_set_speed(i2c, clk_src_in_hz);
    if (ret) {
        dev_err(&pdev->dev, "Failed to set the speed\n");
        return -EINVAL;
    }
    ret = mt_i2c_clock_enable(i2c);
    if (ret) {
        dev_err(&pdev->dev, "clock enable failed!\n");
        return ret;
    }
    mt_i2c_init_hw(i2c);
    mt_i2c_clock_disable(i2c);
    i2c->dma_buf.vaddr = dma_alloc_coherent(&pdev->dev,
        PAGE_SIZE, &i2c->dma_buf.paddr, GFP_KERNEL);
    if (i2c->dma_buf.vaddr == NULL) {
        dev_err(&pdev->dev, "dma_alloc_coherent fail\n");
        return -ENOMEM;
    }
    i2c_set_adapdata(&i2c->adap, i2c);
    /* ret = i2c_add_adapter(&i2c->adap); */
    ret = i2c_add_numbered_adapter(&i2c->adap);
    if (ret) {
        dev_err(&pdev->dev, "Failed to add i2c bus to i2c core\n");
        free_i2c_dma_bufs(i2c);
        return ret;
    }
    platform_set_drvdata(pdev, i2c);

    if (!map_cg_regs(i2c))
        pr_warn("Map cg regs successfully.\n");

    return 0;
}
↓

i2c_add_numbered_adapter() -> __i2c_add_numbered_adapter() -> 
↓

static int i2c_register_adapter(struct i2c_adapter *adap)
{
    int res = 0;

    /* Can't register until after driver model init */
    if (unlikely(WARN_ON(!i2c_bus_type.p))) {
        res = -EAGAIN;
        goto out_list;
    }

    /* Sanity checks */
    if (unlikely(adap->name[0] == '\0')) {
        pr_err("i2c-core: Attempt to register an adapter with "
               "no name!\n");
        return -EINVAL;
    }
    if (unlikely(!adap->algo)) {
        pr_err("i2c-core: Attempt to register adapter '%s' with "
               "no algo!\n", adap->name);
        return -EINVAL;
    }

    rt_mutex_init(&adap->bus_lock);
    mutex_init(&adap->userspace_clients_lock);
    INIT_LIST_HEAD(&adap->userspace_clients);

    /* Set default timeout to 1 second if not already set */
    if (adap->timeout == 0)
        adap->timeout = HZ;

    /* 註冊adapter爲i2c_bus上的device */
    dev_set_name(&adap->dev, "i2c-%d", adap->nr);
    adap->dev.bus = &i2c_bus_type;
    adap->dev.type = &i2c_adapter_type;
    res = device_register(&adap->dev);
    if (res)
        goto out_list;

    dev_dbg(&adap->dev, "adapter [%s] registered\n", adap->name);

    pm_runtime_no_callbacks(&adap->dev);

#ifdef CONFIG_I2C_COMPAT
    res = class_compat_create_link(i2c_adapter_compat_class, &adap->dev,
                       adap->dev.parent);
    if (res)
        dev_warn(&adap->dev,
             "Failed to create compatibility class link\n");
#endif

    /* bus recovery specific initialization */
    if (adap->bus_recovery_info) {
        struct i2c_bus_recovery_info *bri = adap->bus_recovery_info;

        if (!bri->recover_bus) {
            dev_err(&adap->dev, "No recover_bus() found, not using recovery\n");
            adap->bus_recovery_info = NULL;
            goto exit_recovery;
        }

        /* Generic GPIO recovery */
        if (bri->recover_bus == i2c_generic_gpio_recovery) {
            if (!gpio_is_valid(bri->scl_gpio)) {
                dev_err(&adap->dev, "Invalid SCL gpio, not using recovery\n");
                adap->bus_recovery_info = NULL;
                goto exit_recovery;
            }

            if (gpio_is_valid(bri->sda_gpio))
                bri->get_sda = get_sda_gpio_value;
            else
                bri->get_sda = NULL;

            bri->get_scl = get_scl_gpio_value;
            bri->set_scl = set_scl_gpio_value;
        } else if (!bri->set_scl || !bri->get_scl) {
            /* Generic SCL recovery */
            dev_err(&adap->dev, "No {get|set}_gpio() found, not using recovery\n");
            adap->bus_recovery_info = NULL;
        }
    }

exit_recovery:
    /* create pre-declared device nodes */
    /* 循環遍歷adapter node下掛載的其他子node,註冊成爲i2c bus的device */
    of_i2c_register_devices(adap);
    acpi_i2c_register_devices(adap);
    acpi_i2c_install_space_handler(adap);

    if (adap->nr < __i2c_first_dynamic_bus_num)
        i2c_scan_static_board_info(adap);

    /* Notify drivers */
    mutex_lock(&core_lock);
    bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter);
    mutex_unlock(&core_lock);

    return 0;

out_list:
    mutex_lock(&core_lock);
    idr_remove(&i2c_adapter_idr, adap->nr);
    mutex_unlock(&core_lock);
    return res;
}
↓

static void of_i2c_register_devices(struct i2c_adapter *adap)
{
    struct device_node *node;

    /* Only register child devices if the adapter has a node pointer set */
    if (!adap->dev.of_node)
        return;

    dev_dbg(&adap->dev, "of_i2c: walking child nodes\n");

    /* 遍歷adapter node下的子node,並創建標準的i2c bus的device */
    for_each_available_child_of_node(adap->dev.of_node, node) {
        if (of_node_test_and_set_flag(node, OF_POPULATED))
            continue;
        of_i2c_register_device(adap, node);
    }
}
↓

static struct i2c_client *of_i2c_register_device(struct i2c_adapter *adap,
                         struct device_node *node)
{
    struct i2c_client *result;
    struct i2c_board_info info = {};
    struct dev_archdata dev_ad = {};
    const __be32 *addr_be;
    u32 addr;
    int len;

    dev_dbg(&adap->dev, "of_i2c: register %s\n", node->full_name);

    if (of_modalias_node(node, info.type, sizeof(info.type)) < 0) {
        dev_err(&adap->dev, "of_i2c: modalias failure on %s\n",
            node->full_name);
        return ERR_PTR(-EINVAL);
    }

    addr_be = of_get_property(node, "reg", &len);
    if (!addr_be || (len < sizeof(*addr_be))) {
        dev_err(&adap->dev, "of_i2c: invalid reg on %s\n",
            node->full_name);
        return ERR_PTR(-EINVAL);
    }

    addr = be32_to_cpup(addr_be);
    if (addr & I2C_TEN_BIT_ADDRESS) {
        addr &= ~I2C_TEN_BIT_ADDRESS;
        info.flags |= I2C_CLIENT_TEN;
    }

    if (addr & I2C_OWN_SLAVE_ADDRESS) {
        addr &= ~I2C_OWN_SLAVE_ADDRESS;
        info.flags |= I2C_CLIENT_SLAVE;
    }

    if (i2c_check_addr_validity(addr, info.flags)) {
        dev_err(&adap->dev, "of_i2c: invalid addr=%x on %s\n",
            info.addr, node->full_name);
        return ERR_PTR(-EINVAL);
    }

    info.addr = addr;
    info.of_node = of_node_get(node);
    info.archdata = &dev_ad;

    if (of_get_property(node, "wakeup-source", NULL))
        info.flags |= I2C_CLIENT_WAKE;

    result = i2c_new_device(adap, &info);
    if (result == NULL) {
        dev_err(&adap->dev, "of_i2c: Failure registering %s\n",
            node->full_name);
        of_node_put(node);
        return ERR_PTR(-EINVAL);
    }
    return result;
}

3.2.1 mz_mag_driver

具體的I2c設備驅動,在總線驅動使用of_i2c_register_devices()創建設備以後,就可以適配工作了。

dts:

arch\arm64\boot\dts\mediatek\mt6799.dtsi:
    i2c1: i2c@11090000 {
        compatible = "mediatek,mt6799-i2c";
        id = <1>;
        reg = <0 0x11090000 0 0x1000>,
            <0 0x11000100 0 0x80>;
        interrupts = <GIC_SPI 85 IRQ_TYPE_LEVEL_LOW>;
        clocks = <&pericfg CLK_PERICFG_RG_I2C1_BCLK>, <&pericfg CLK_PERICFG_RG_AP_DM>;
        clock-names = "main", "dma";
        clock-div = <5>;
    };

arch\arm64\boot\dts\mediatek\mz6799_6m_v2_2k_n.dtsi:
&i2c1 {
    apds9922:apds9922@53 {
        compatible = "mediatek,apds9922";
        interrupt-parent = <&eintc>;
        interrupts = < 8 IRQ_TYPE_EDGE_FALLING>;
        debounce = <8 0>;
        gpio = < 8 >;
        reg = <0x53>;
        status = "okay";
    };
}

driver:

drivers\iio\magnetometer\mz_mag.c:
static const struct i2c_device_id mz_mag_id[] = {
    {"mediatek,mmc3530", 0 },
    {"mediatek,akm09911", 1 },
    { }
};

static struct i2c_driver mz_mag_driver = {
    .probe     = mz_mag_probe,
    .id_table  = mz_mag_id,
    .driver = {
        .name  = MZ_MAG_DEV_NAME,
        .owner = THIS_MODULE,
        #ifdef CONFIG_OF
        .of_match_table = of_match_ptr(msensor_of_match),
        #endif
    },
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章