Menu Close

Device Tree Root Node Compatible Property

This article talks about the standard properties in device tree nodes.

For related subjects, please refer to the SOC Table of Contents.

 

Every node, including the root node, has compatible. The compatible property for the zynq-7000.dtsi file is as follows:

/{      //This is line 15 in the file
        #address-cells = <1>;
        #size-cells = <1>;
        compatible = “xlnx,zynq-7000”;
        ...
}       //Note that the segment ends on line 431 in the file

We can see that compatible has the value of “xlnx,zynq-7000”. As mentioned before, the compatible in the device node is used to match Linux kernel drivers. In this case in the root kernel, the compatible property is used to see if the processor being used is compatible. The formatting is also the same (manufacturer, model), consisting of strings. Here, it would be “xlnx” as the manufacturer and “zynq-7000” as the model.

 

Verification before using the device tree

Before using the device tree, U-Boot would transfer a machine id value to the Linux kernel, and describe its hardware platform to see if the Linux kernel has support and is compatible.

Note that the Linux kernel supports many different hardware platforms, but it uses MACHINE_START and MACHINE_END to define a machine_desc structure for each specific board.

An example is lines 613 to 623 of mach-mx35_3ds.c in /arch/arm/mach-imx/.

  MACHINE_START(MX35_3DS,“Freescale MX35PDK”)
         /* Maintainer: Freescale Semiconductor, Inc */
         .atag_offset = 0x100,
         .map_io = mx35_map_io,
         .init_early = imx35_init_early,
         .init_irq = mx35_init_irq,
         .init_time = mx35pdk_timer_init,
         .reserve = mx35_3ds_reserve,
         .restart = mxc_restart,
  MACHINE_END

The above section of code defines the “Freescale MX35PDK” hardware platform, where MACHINE_START and MACHINE_END are located in arch.h in /arch/arm/include/asm/mach/arch.h. Its contents are as follows:

  #define MACHINE_START(_type,_name)                   \
  static const struct machine_desc__mach_desc_##_type  \
  __used                                               \
  __attribute__((__section__(“.arch.info.init”))) = {  \
  .nr = MACH_TYPE_##_type,                             \
  .name = _name,

  #define MACHINE_END                                  \
  };

Based off of the macro definitions of MACHINE_START and MACHINE_END, the expanded version of the previous code segment would look like the following:

  static const struct machine_desc__mach_desc_MX35_3DS         \
  __used                                                       \
  __attribute__((__section__(“.arch.info.init”))) = {
         .nr = MACH_TYPE_MX35_3DS,
         .name = “Freescale MX35PDK”,
         /* Maintainer: Freescale Semiconductor, Inc */
         .atag_offset = 0x100,
         .map_io = mx35_map_io,
         .init_early = imx35_init_early,
         .init_irq = mx35_init_irq,
         .init_time = mx35pdk_timer_init,
         .reserve = mx35_3ds_reserve,
         .restart = mxc_restart,
  };

We can see that the machine_desc type structure variable __mach_desc_MX35_3DS is defined, and it is stored in .arch.info.init. The MACH_TYPE_MX35_3DS on line 4 is the machine id for “Freescale MX35PDK”. The definition of MACH_TYPE_MX35_3DS is located in the mach-types.h file in /include/generated/, where a large number of machine ids are defined.

We can see that MACH_TYPE_MX35_3DS is on line 286, which has the value of 1645.

As mentioned before, U-Boot will transfer the machine id to the Linux kernel, where it will be inspected. It is taking the MACH_TYPE value and comparing it to see if there are any matches; matches means the kernel supports the hardware platform, whereas having no matches would result in the kernel failing to boot.

 

Device matching after using the device tree

When the Linux kernel is introduced to the device tree, MACHINE_START is no longer used and is replaced with DT_MACHINE_START. Note that DT_MACHINE_START is defined in arch.h under the directory /arch/arm/include/asm/mach/, with the following definition:

  #define DT_MACHINE_START(_name,_namestr)             \
  static const struct machine_desc__mach_desc_##_name  \
  __used                                               \
  __attribute__((__section__(“.arch.info.init”)))={    \
         .nr = ~0,                                     \
         .name = _namestr,
  };

We can see that DT_MACHINE_START is similar to MACHINE_START, with the difference being .nr settings. In DT_MACHINE_START .nr can be set as ~0, which means the machine id won’t be used to check if the Linux kernel supports specific hardware platforms after introducing the device tree.

Opening the common.c file under arch/arm/mach-zynq/ we would see the following from line 191 to 213:

  static const char* const zynq_dt_match[]={
         “xlnx,zynq-7000”,
         NULL
  };

  DT_MACHINE_START(XILINX_EP107,“Xilinx Zynq Platform”)
         /* 64KB way size, 8-way associativity, parity disabled */
  #ifdef CONFIG_XILINX_PREFETCH
         .12c_aux_val = 0x30400000,
         .12c_aux_mask = 0xcfbfffff,
  #else
         .12c_aux_val = 0x00400000,
         .12c_aux_mask = 0xffbfffff,
  #endif
         .smp = smp_ops(zynq_smp_ops),
         .map_io = zynq_map_io,
         .init_irq = zynq_irq_init,
         .init_machine = zynq_init_machine,
         .init_late = zynq_timer_late,
         .init_time = zynq_timer_init,
         .dt_compat = zynq_dt_match,
         .reserve = zynq_memory_init,
  MACHINE_END

The .dt_compat member variable in machine_desc holds the compatibility properties of the hardware platform. The settings for the definitions of .dt_compat = zynq_dt_match and the zynq_dt_match arrays are in the first 4 lines of the code above, and we can see that matching string is “xlnx,zynq-7000”. If the board has a device tree root node compatible property value that matches with any value in the zynq_dt_match table, then it means the Linux kernel supports the development board and the hardware platform. The device tree file we use is system-top.dts, which uses the include command to include zynq-7000.dtsi. The compatible property in zynq-7000.dtsi’s root node has the value of “xlnx,zynq-7000”, which means the kernel supports the development board.

If the compatible property value under the root node of zynq-7000.dtsi had a different value, the Linux kernel would be unable to find a matching hardware platform, resulting in the kernel being unable to boot.

We will now briefly show how the Linux kernel matches the corresponding machine_desc according to the compatible property of the root node of the device tree. The Linux kernel calls the start_kernel function to start the kernel, and the start_kernel function calls the setup_arch function to match the machine_desc. The setup_arch function is defined in setup.c under arch/arm/kernel/. The function is between lines 913 to 986 in setup.c as follows. Note that lines 923 to 985 are not shown.

  void__init setup_arch(char **cmdline_p)
  {
         const struct machine_desc *mdesc;

         setup_processor();
         mdesc = setup_machine_fdt(__atags_pointer);
         if(!mdesc)
         mdesc=setup_machine_tags(__atags_pointer,__machine_arch_type);
         machine_desc = mdesc;
         machine_name = mdesc->name;
         ...
  }

In the code segment above, the setup_machine_fdt function is used to obtain matched machine_desc, where the parameter is the starting address of atags, which is also the starting address of the dtb file that U-Boot transfers to Linux. The return value of setup_machine_fdt is the successfully found and matched machine_desc.

The definition of setup_machine_fdt is in arch/arm/kernel/devtree.c, which has the following content between lines 204 and 250, where lines 207 to 213 and lines 220 to 246 are not shown.

  const struct machine_desc* __init setup_machine_fdt(unsigned int dt_phys)
  {
  const struct machine_desc *mdesc, *mdesc_best = NULL;
  ...

  if(!dt_phys||!early_init_dt_verify(phys_to_virt(dt_phys)))
         return NULL;

  mdesc = of_flat_dt_match_machine(mdesc_best,arch_get_next_match);
  ...
  __machine_arch_type = mdesc->nr;

  return mdesc;
  }

On line 218, the function of_flat_dt_match_machine is used to obtain the corresponding machine_desc. mdesc_best is the default machine_desc, and the parameter arch_get_next_mach is a function, where its definition is in arch/arm/kernel/devtree.c. Using the corresponding machine_desc requires using the compatible property value of the root node of the device tree, as well as the .dt_compat machine_desc structures stored in the Linux kernel; they are compared, and if any matches are found it will mean a matching machine_desc was located. The purpose of the arch_get_next_mach function is to obtain the next machine_descstructure from the Linux kernel.

Lines 705 to 743 of the of_flat_dt_match_machine function under drivers/of/fdt.c are shown below. Note lines 722 to 738 are not shown.

  const void* __init of_flat_dt_match_machine(const void *default_match,
         const void*(*get_next_compat)(const char* const**))
  {
         const void *data = NULL;
         const void *best_data = default_match;
         const char *const *compat;
         unsigned long dt_root;
         unsigned int best_score = ~1, score = 0;

         dt_root = of_get_flat_dt_root();
         while((data = get_next_compat(&compat))){
                 score = of_flat_dt_match(dt_root,compat);
                 if(score > 0 && score < best_score){
                         best_data = data;
                         best_score = score;
                 }
         }
         ...

         pr_info(“Machine model:%s\n”,of_flat_dt_get_machine_name());

         return best_data;
  }

The of_get_flat_dt_root function on line 714 obtains the device tree’s root node.

The while loop on lines 715 to 720 is used to search machine_desc that match. The of_flat_dt_match function on line 716 compares the property value of compatible in the root node to each .dt_compat value of machine_desc structures until a matching machine_desc is found.

The process that the Linux kernel uses to find a compatible machine_desc is summarized below:

  1. start_kernel()
  2. setup_arch()
  3. setup_machine_fdt()
  4. of_flat_dt_match_machine()
  5. dt_root = of_get_flat_dt_root();
     while((data = get_next_compat(&compat))){
          score = of_flat_dt_match(dt_root,compat);
          if(score > 0 && score < best_score){
               best_data = data;
               best_score = score;
          }
     }

 

Posted in Textbook and Training Project

Related Articles

Leave a Reply

Your email address will not be published.

Leave the field below empty!