Linux-device tree

 

One: Device tree concept

        In the early days of Linux kernel development, the board-level files of the arm architecture regarding SOC and development boards and their peripherals were extremely large. With the birth of more and more SOCs and development boards, their board-level files increased exponentially, and more and more The "junk files" are compiled into the Linux kernel. Linus has something to say about this: "This whole ARM thing is a fucking pain in the ass", arm things are terrible, so arm introduced the device tree technology in the powerpc architecture to describe the content of board-level information. It is separated from Linux and described in a proprietary file format. This file is the device tree with the file extension ".dts". The common information of different development boards made by the same SOC is extracted to the ".dtsi" file. The Linux kernel uses the DTB binary file compiled by the DTC tool from the DTS source file. The DTC tool source code is located in the linux kernel scripts/dtc directory. The device tree source file DTS is located in arch/arm/boot/dts. The command to compile the device tree is "Make dtbs".

Two: device tree syntax

1. Device node

/ {
	aliases {
		can0 = &flexcan1;
		can1 = &flexcan2;
        ... ...
	};

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

		cpu0: cpu@0 {
			compatible = "arm,cortex-a7";
			device_type = "cpu";
			reg = <0>;
        ... ..
        }
    }
}

The device tree uses a tree structure to describe the device information files on the board. Each device is a node, called a device node. Each node describes the node information through some attribute information, aliases and cpus are the root nodes "/" The two child nodes below, and cpu0 is the child node below cpus. Device node naming format: " label:node-name@unit-address"

"Label": The label of the node, which can be accessed directly through &label. For the node "cpu0:cpu@0", you can access the node "cpu@0" through &cpu0.

"Node-name": The name of the node, an ASCII string. The name of the node can be cleaned to describe the function of the node. For example, "spi1" means that the node is a spi1 peripheral.

"Unit-address": generally indicates the address of the device or the first address of the register, if not, it can be omitted. For example, "cpu@0"

2. Node attributes

Nodes are composed of a bunch of "attributes". Nodes are all specific devices. Different attributes require different attributes. Users can also customize attributes.

(1) Compatible: The "compatibility" attribute, whose value is a string list, is used to bind the device and the driver. The string list is used to select the driver to be used by the device, and the device will sequentially match and find the corresponding driver file from this string list.

Format: "manufacturer, model", manufacturer means manufacturer, model means driver name corresponding to the module.

Example: compatible = "fsl, imx6ul-evk-wm8960", "fsl, imx-audio-wm8960" means that the manufacturer Freescale produces and uses the vm8960 audio chip driver module. The driver file mx-wm8960.c will have the following content to complete the adaptation with the device tree file.

sound {
    compatible = "fsl,imx6ul-evk-wm8960",
    "fsl,imx-audio-wm8960";
}
static const struct of_device_id imx_wm8960_dt_ids[] = {
	{ .compatible = "fsl,imx-audio-wm8960", },
	{ /* sentinel */ }
};

(2) model: The attribute value is a string, which generally describes device module information.

sound {
    model = "wm8960-audio";
}

(3) Status: The attribute value is a string, which means the device status

"Okay": indicates that the device is operable,

"Disabled": indicates that the device is currently inoperable and may become operable in the future, such as hot-swappable devices.

"Fail": indicates that the device is not operable. The device has detected a series of errors and is unlikely to become operable in the future.

"Fail-sss": It means the same as "fail", sss is the detected error content.

(4) #address-cells and #size-cells: The attribute value is unsigned 32-bit integer, used to describe the address information of the child node.

The #address-cells attribute value determines the word length (32 bits) of the address information in the reg attribute of the child node .

The #size-cells attribute value determines the word length (32 bits) of the length information in the reg attribute of the child node .

(5) reg: attribute value and address correlation including address length.

格式:reg = <address1 length1 address2 length2 address3 length3…………>

Each "address length" combination represents an address range, address is the starting address, and length is the address length. #address-cells indicates the word length occupied by the address data, and #size-cells indicates the word length occupied by the length data.

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

	gpio_spi: gpio_spi@0 {
		reg = <0>;
	};
};

aips1: aips-bus@02000000 {
    #address-cells = <1>;
    #size-cells = <1>;

    pwm1: pwm@02080000 {
        reg = <0x02080000 0x4000>;
    }    
}

(6) Ranges: The attribute value can be empty or a numeric matrix written in the format of (chile-bus-address, parent-bus-address, length).

chile-bus-address: the physical address of the sub-bus address space, the word length occupied by this physical address is determined by the #address-cells of the parent node

parent-bus-address: The physical address of the parent bus address space. The #address-cells of the parent node also determines the word length occupied by this physical address.

length: The length of the sub-address space.

demo_level {
    compatible = "simple-bus";
    ranges = <0x0 0x30000000 0x3000>;
    #address-cells = <1>;
    #size-cells = <1>;

    range@0 {
        compatible = "range";
        reg = <0x100 0x200>;
        reg-names = "range0";
    };
}

The node demo-level attribute ranges value is <0x0, 0x30000000, 0x3000>, which means that an address range of size 0x3000 is specified, the physical starting address of the child address space is 0x0, and the physical starting address of the parent address space is 0x30000000. The reg attribute in the node range@0 defines the starting address of the register as 0x100 and the register length as 0x100. After address conversion, the start address of the range0 device can be obtained as: 0x30000100=0x30000000+0x100, and the end address is: 0x30000300=0x30000000+0x100+0x200.

(7) name: The attribute value is a string, and the node name is recorded.

(8) device_type: The attribute value is a string. This attribute can only be used for cpu nodes or memory nodes for the device tree. See (two, 1 illustration)

Three: Device tree and device matching method

         Each node has the "compatible" attribute, and the root node "/" also exists. The Linux kernel will check whether the device is supported through the compatible attribute of the root node, and start the Linux kernel if it does.

/ {
	model = "Freescale i.MX6 ULL 14x14 EVK Board";
	compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";
}

1. Before introducing the device tree

The Linux kernel will judge whether the device is supported according to a machine id value passed by uboot. The data format of each device described in the Linux kernel is as follows:

MACHINE_START(S3C2440, "SMDK2440")
	/* Maintainer: Ben Dooks <[email protected]> */
	.atag_offset	= 0x100,

	.init_irq	= s3c2440_init_irq,
	.map_io		= smdk2440_map_io,
	.init_machine	= smdk2440_machine_init,
	.init_time	= smdk2440_init_time,
MACHINE_END

The prototype of MACHINE_START() is:

#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				\
};

Bring in the parameters "S3c2440" and "SMDK2440" to get ".nr = MACH_TYPE_S3C2440; .name = SMDK2440", where MACH_TYPE_S3C2440 is the machine id of the SMDK2440 development board, which is defined in uboot's include/generated/mach-types.h File, the data is stored in the ".arch.info.init" section. The Linux kernel judges whether the device is supported based on the machine id.

2. After introducing the device tree

The data format of the Linux kernel describing the device is changed to the following. Through observation, it can be found that the setting of ".nr" is no longer the same, which proves that after the introduction of the device tree, the machine id will no longer be used to determine whether the Linux kernel supports a certain device.

#define DT_MACHINE_START(_name, _namestr)		\
static const struct machine_desc __mach_desc_##_name	\
 __used							\
 __attribute__((__section__(".arch.info.init"))) = {	\
	.nr		= ~0,				\
	.name		= _namestr,
static const char *imx6ul_dt_compat[] __initconst = {
	"fsl,imx6ul",
	"fsl,imx6ull",
	NULL,
};

DT_MACHINE_START(IMX6UL, "Freescale i.MX6 Ultralite (Device Tree)")
	.map_io		= imx6ul_map_io,
	.init_irq	= imx6ul_init_irq,
	.init_machine	= imx6ul_init_machine,
	.init_late	= imx6ul_init_late,
	.dt_compat	= imx6ul_dt_compat,
MACHINE_END

The ".dt_compat" member variable in the machine_desc structure holds the compatibility attributes of the device. The attribute table imx6ul_dt_compat contains "fsl, imx6ul" and "fsl, imx6ull". As long as the compatible attribute value of the root node "/" of a certain device is If any value in the imx6ul_dt_compat table matches, it means that the Linux kernel supports this device.

3. The Linux kernel matches machine_desc with the device tree to start the Linux kernel process analysis

There is a sub-function "setup_arch(&command_line);" in the Linux kernel startup function start_kernel. The kernel will use the setup_arch function to match machine_desc

asmlinkage __visible void __init start_kernel(void)
{

	lockdep_init();
    ... ...
	setup_arch(&command_line);
    ... ...
}
void __init setup_arch(char **cmdline_p)
{
	const struct machine_desc *mdesc;

	setup_processor();
	mdesc = setup_machine_fdt(__atags_pointer);                           /* (1) */
	if (!mdesc)
		mdesc = setup_machine_tags(__atags_pointer, __machine_arch_type);
	machine_desc = mdesc;
	machine_name = mdesc->name;
	dump_stack_set_arch_desc("%s", mdesc->name);

    ... ...
}

Call the function at (1) to get the matching machine_desc, the parameter is the first address of atags, which is the first address of the dtb file passed by uboot to the Linux kernel, and the return value mdesc is the most matching machine_desc found.

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_mach);          /* (2) */

	__machine_arch_type = mdesc->nr;

	return mdesc;
}

Call the function at (2) to get the matching machine_desc, the parameter mdesc_best is the default machine_desc. The process of finding a matching machine_desc is to compare the value of the compatible attribute of the root node of the device tree with the value in the ".dt_compat" of the machine_desc structure stored in the Linux kernel. If they are equal, it means that a matching machine_desc is found.

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();                                      /* (3) */
	while ((data = get_next_compat(&compat))) {
		score = of_flat_dt_match(dt_root, compat);                        /* (4)*/
		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;

}

Obtain the root and node of the device tree through the function at (3). The while loop is the process of finding the matching machine_desc. The function at (4) will perform the process of the conpatible attribute of the root node with the value of .dt_compat in each machine_desc structure. Compare.

4. The Linux kernel parses the DTB file

asmlinkage __visible void __init start_kernel(void)
{
    setup_arch(&command_line);
}

void __init setup_arch(char **cmdline_p)
{
    unflatten_device_tree();
}

void __init unflatten_device_tree(void)
{
    __unflatten_device_tree(initial_boot_params, &of_root,
        early_init_dt_alloc_memory_arch);
}

static void __unflatten_device_tree(void *blob,
			     struct device_node **mynodes,
			     void * (*dt_alloc)(u64 size, u64 align))
{
    size = (unsigned long)unflatten_dt_node(blob, NULL, &start, NULL, NULL, 0, true); 
}

Four: common operation functions of the device tree

The device tree describes the detailed information of the device. This information includes numeric type, string type, and array type. The Linux kernel provides a series of functions to obtain this information.

Devices exist in the device tree file in the form of nodes. In order to obtain the attribute information of these devices, the Linux kernel uses the device_node structure to describe a node.

1. Find the function of the node

struct device_node {
	const char *name;
	const char *type;
	phandle phandle;
	const char *full_name;
	struct fwnode_handle fwnode;

	struct	property *properties;
	struct	property *deadprops;	/* removed properties */
	struct	device_node *parent;
	struct	device_node *child;
	struct	device_node *sibling;
	struct	kobject kobj;
	unsigned long _flags;
	void	*data;
#if defined(CONFIG_SPARC)
	const char *path_component_name;
	unsigned int unique_id;
	struct of_irq_controller *irq_trans;
#endif
};

(1) Find the specified node by the node name

struct device_node *of_find_node_by_name(struct device_node *from,
	const char *name)

(2) Find the specified node by the node type type, that is, the device_type attribute value

struct device_node *of_find_node_by_type(struct device_node *from,
	const char *type)

(3) Find the specified node through the two attributes of device_type and compatible

struct device_node *of_find_compatible_node(struct device_node *from,
	const char *type, const char *compatible)

(4) Find the specified node through the of_device_id matching table

static inline struct device_node *of_find_matching_node_and_match(
	struct device_node *from,
	const struct of_device_id *matches,
	const struct of_device_id **match)

(5) Find the specified node through the path

static inline struct device_node *of_find_node_by_path(const char *path)

2. Find the function of parent/child node

(1) Get the parent node of the specified node

struct device_node *of_get_parent(const struct device_node *node)

(2) Get child nodes

struct device_node *of_get_next_parent(struct device_node *node)

3. Get the attribute value function

struct property {
	char	*name;
	int	length;
	void	*value;
	struct property *next;
	unsigned long _flags;
	unsigned int unique_id;
	struct bin_attribute attr;
};

(1) Find the specified attribute

struct property *of_find_property(const struct device_node *np,
				  const char *name,
				  int *lenp)

(2) Get the number of elements in the attribute, for example, get the array size of the reg array attribute

int of_property_count_elems_of_size(const struct device_node *np,
				const char *propname, int elem_size)

(3) Get the u32 type data value of the specified label in the attribute

static inline int of_property_read_u32_index(const struct device_node *np,
			const char *propname, u32 index, u32 *out_value)

(4) Get the array data of types u8, u16, u32, u64 in the attribute

static inline int of_property_read_u8_array(const struct device_node *np,
			const char *propname, u8 *out_values, size_t sz)

static inline int of_property_read_u16_array(const struct device_node *np,
			const char *propname, u16 *out_values, size_t sz)

static inline int of_property_read_u32_array(const struct device_node *np,
					     const char *propname,
					     u32 *out_values, size_t sz)

static inline int of_property_read_u64_array(const struct device_node *np,
					     const char *propname,
					     u64 *out_values, size_t sz)

(5) Get the integer value in the attribute

int of_property_read_u8(const struct device_node *np,
                        const char *propname,
                        u8 *out_value)

int of_property_read_u16(const struct device_node *np,
                        const char *propname,
                        u16 *out_value)

int of_property_read_u32(const struct device_node *np,
                        const char *propname,
                        u32 *out_value)

int of_property_read_u64(const struct device_node *np, 
                        const char *propname,
                        u64 *out_value)

(6) Get the string value in the attribute

int of_property_read_string(struct device_node *np, const char *propname,
				const char **out_string)

(7) Get the #address_cells and #size_cells values

int of_n_addr_cells(struct device_node *np);
int of_n_size_cells(struct device_node *np);

4. Other commonly used functions

(1) Check whether the compatible attribute of the node contains the string specified by compat

int of_device_is_compatible(const struct device_node *device,
		const char *compat)

(2) Get address-related attributes, "reg" or "assigned-address"

const __be32 *of_get_address(struct device_node *dev, int index, u64 *size,
		    unsigned int *flags)

(3) Convert the address read from the device tree to a physical address

u64 of_translate_address(struct device_node *dev, const __be32 *in_addr)

(4) Get the resource "resource" of peripherals such as IIc and SPI

struct resource {
	resource_size_t start;
	resource_size_t end;
	const char *name;
	unsigned long flags;
	struct resource *parent, *sibling, *child;
};

int of_address_to_resource(struct device_node *dev, int index,
			   struct resource *r)

(5) User direct memory mapping, replacing the function of ioremap

void __iomem *of_iomap(struct device_node *np, int index)

 

Guess you like

Origin blog.csdn.net/qq_34968572/article/details/103183058